From fa0f1b80ab818b213649a9d7626184c8af9bac0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Fri, 7 Feb 2025 18:52:30 +0100 Subject: [PATCH 01/13] feat: add task manager screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- package-lock.json | 48 ++++ package.json | 8 +- src/agent.ts | 7 +- src/agents/utils.ts | 20 ++ src/base/audit-log.ts | 45 +++ src/helpers/tmux-logger copy.ts | 92 ++++++ src/helpers/tmux-logger.ts | 105 +++++++ src/tasks/task-manager.ts | 115 +++++--- src/tasks/task-state-logger.ts | 56 ++++ src/ui/task-monitor/monitor.ts | 464 +++++++++++++++++++++++++++++++ src/ui/task-monitor/ui-config.ts | 120 ++++++++ src/utils/objects.ts | 34 +++ src/utils/text.ts | 6 + src/utils/time.ts | 17 ++ start.sh | 94 +++++++ 15 files changed, 1190 insertions(+), 41 deletions(-) create mode 100644 src/agents/utils.ts create mode 100644 src/base/audit-log.ts create mode 100644 src/helpers/tmux-logger copy.ts create mode 100644 src/helpers/tmux-logger.ts create mode 100644 src/tasks/task-state-logger.ts create mode 100644 src/ui/task-monitor/monitor.ts create mode 100644 src/ui/task-monitor/ui-config.ts create mode 100644 src/utils/objects.ts create mode 100644 src/utils/text.ts create mode 100644 src/utils/time.ts create mode 100755 start.sh diff --git a/package-lock.json b/package-lock.json index 64ae625..0a765a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,11 @@ "@google-cloud/vertexai": "^1.9.2", "@ibm-generative-ai/node-sdk": "^3.2.4", "@opentelemetry/sdk-node": "^0.57.0", + "@types/blessed": "^0.1.25", "bee-agent-framework": "^0.0.61", "bee-observe-connector": "^0.0.6", + "blessed": "^0.1.81", + "chokidar": "^4.0.3", "dotenv": "^16.4.5", "groq-sdk": "^0.7.0", "ollama": "^0.5.11", @@ -1489,6 +1492,14 @@ "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.21.tgz", "integrity": "sha512-v+49JBiG1kmc/9Ug79Lz9wyKaRocBgCnpRaLpdy7p0d3ICKtOAfc/H/Epa1j3F6YdnzjnZKKrnJ8xnh/v1P8Aw==" }, + "node_modules/@types/blessed": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@types/blessed/-/blessed-0.1.25.tgz", + "integrity": "sha512-kQsjBgtsbJLmG6CJA+Z6Nujj+tq1fcSE3UIowbDvzQI4wWmoTV7djUDhSo5lDjgwpIN0oRvks0SA5mMdKE5eFg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -2121,6 +2132,17 @@ "node": "*" } }, + "node_modules/blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", + "bin": { + "blessed": "bin/tput.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2254,6 +2276,20 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/cjs-module-lexer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", @@ -4821,6 +4857,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/readdirp": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", + "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", diff --git a/package.json b/package.json index 2b12a8c..6a774fb 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,10 @@ "url": "https://github.com/i-am-bee/bee-agent-framework-starter/issues" }, "scripts": { - "start": "tsx --inspect --no-warnings src/agent.js", + "start": "NODE_ENV=development npm run startx", + "startx": "chmod +x ./start.sh && ./start.sh", + "start:dev": "tsx --inspect --no-warnings src/agent.js", + "task:monitor": "tsx --inspect --no-warnings src/ui/task-monitor/monitor.js", "ts:check": "tsc --noEmit --project tsconfig.json", "build": "rimraf dist && tsc", "lint": "eslint", @@ -35,8 +38,11 @@ "@google-cloud/vertexai": "^1.9.2", "@ibm-generative-ai/node-sdk": "^3.2.4", "@opentelemetry/sdk-node": "^0.57.0", + "@types/blessed": "^0.1.25", "bee-agent-framework": "^0.0.61", "bee-observe-connector": "^0.0.6", + "blessed": "^0.1.81", + "chokidar": "^4.0.3", "dotenv": "^16.4.5", "groq-sdk": "^0.7.0", "ollama": "^0.5.11", diff --git a/src/agent.ts b/src/agent.ts index 31ce7e1..824fcf5 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -8,6 +8,8 @@ import * as operator from "./agents/operator.js"; import * as supervisor from "./agents/supervisor.js"; import { createConsoleReader } from "./helpers/reader.js"; import { TaskManager } from "./tasks/task-manager.js"; +import { getLogger, LoggerType } from "./helpers/tmux-logger.js"; +import { agentIdToString } from "./agents/utils.js"; const registry = new AgentRegistry({ async onCreate( @@ -17,7 +19,7 @@ const registry = new AgentRegistry({ ): Promise<{ agentId: string; instance: BeeAgent }> { const { kind: agentKind, type: agentType, instructions, description } = config; const num = poolStats.created + 1; - const agentId = `${agentKind}:${agentType}[${num}]`; + const agentId = agentIdToString({ agentKind, agentType, num }); const tools = config.tools == null ? toolsFactory.getAvailableToolsNames() : config.tools; const instance = createAgent( { @@ -108,6 +110,8 @@ const { instance: supervisorAgent } = await registry.acquireAgent( // Can you generate poem for each of these topics: love, day, night? // Can you get list of articles about each of these topics: deepseek, interstellar engine, agi? +const supervisorLogger = getLogger(LoggerType.AGENT, "supervisor"); + const reader = createConsoleReader({ fallback: "What is the current weather in Las Vegas?" }); for await (const { prompt } of reader) { try { @@ -126,6 +130,7 @@ for await (const { prompt } of reader) { ) .observe((emitter) => { emitter.on("update", (data, meta) => { + supervisorLogger; reader.write( `${(meta.creator as any).input.meta.name} 🤖 (${data.update.key}) :`, data.update.value, diff --git a/src/agents/utils.ts b/src/agents/utils.ts new file mode 100644 index 0000000..b4d9be1 --- /dev/null +++ b/src/agents/utils.ts @@ -0,0 +1,20 @@ +export interface AgentId { + agentKind: string; + agentType: string; + num: number; +} + +export function agentIdToString(agentId: AgentId) { + return `${agentId.agentKind}:${agentId.agentType}[${agentId.num}]`; +} + +export function stringToAgentId(agentId: string): AgentId { + const [kind, rest] = agentId.split(":"); + const match = rest.match(/([^[]+)(?:\[(\d+)\])?/); + + return { + agentKind: kind, + agentType: match?.[1] ?? "", + num: match?.[2] ? parseInt(match[2]) : -1, + }; +} diff --git a/src/base/audit-log.ts b/src/base/audit-log.ts new file mode 100644 index 0000000..0b44565 --- /dev/null +++ b/src/base/audit-log.ts @@ -0,0 +1,45 @@ +import { appendFileSync, existsSync, renameSync, writeFileSync } from "fs"; +import { join } from "path"; + +export interface LogUpdate { + timestamp: string; + type: TType; + taskId: string; + data: TData; +} + +export class BaseAuditLog { + protected logPath: string; + + constructor(logFileDefaultPath: readonly string[], logFileDefaultName: string, logPath?: string) { + if (!logPath) { + this.logPath = join(process.cwd(), ...logFileDefaultPath, `${logFileDefaultName}.log`); + } else { + this.logPath = logPath; + } + this.rotateLogFileIfExists(); + } + + private rotateLogFileIfExists(): void { + if (existsSync(this.logPath)) { + // Generate timestamp for the backup file + const timestamp = new Date() + .toISOString() + .replace(/:/g, "-") // Replace colons with dashes for file name compatibility + .replace(/\./g, "-"); // Replace dots with dashes + + // Create backup file path + const backupPath = this.logPath.replace(".log", `.${timestamp}.log`); + + // Rename existing file to backup + renameSync(this.logPath, backupPath); + + // Create new empty log file + writeFileSync(this.logPath, ""); + } + } + + protected logUpdate(update: LogUpdate) { + appendFileSync(this.logPath, JSON.stringify(update) + "\n"); + } +} diff --git a/src/helpers/tmux-logger copy.ts b/src/helpers/tmux-logger copy.ts new file mode 100644 index 0000000..e528439 --- /dev/null +++ b/src/helpers/tmux-logger copy.ts @@ -0,0 +1,92 @@ +import { pino } from "pino"; +import fs from "fs"; +import { AgentKind } from "src/agents/agent-registry.js"; + +// Ensure logs directory exists +if (!fs.existsSync("logs")) { + fs.mkdirSync("logs"); +} + +// Create separate log files for different components +const supervisorLogger = pino( + { + level: process.env.LOGGER_LEVEL || "info", + }, + pino.destination("logs/supervisor_agents.log"), +); + +const registryLogger = pino( + { + level: process.env.LOGGER_LEVEL || "info", + }, + pino.destination("logs/agent_registry.log"), +); + +const taskManagerLogger = pino( + { + level: process.env.LOGGER_LEVEL || "info", + }, + pino.destination("logs/task_manager.log"), +); + +// Cache for operator loggers +const operatorLoggers = new Map(); + +export enum LoggerType { + AGENT = "agent", + REGISTRY = "registry", + TASK_MANAGER = "taskManager", +} + +export function getLogger(type: LoggerType, agentKind: AgentKind | null = null, operatorId = null) { + switch (type) { + case "registry": + return registryLogger; + case "taskManager": + return taskManagerLogger; + case "agent": + switch (agentKind) { + case "supervisor": + return supervisorLogger; + case "operator": + // If operatorId is provided, create/get specific operator logger + if (operatorId !== null) { + if (!operatorLoggers.has(operatorId)) { + operatorLoggers.set( + operatorId, + pino( + { level: process.env.LOGGER_LEVEL || "info" }, + pino.destination(`logs/operator_${operatorId}_agents.log`), + ), + ); + } + return operatorLoggers.get(operatorId); + } + // Default operator logger for backward compatibility + return pino( + { level: process.env.LOGGER_LEVEL || "info" }, + pino.destination("logs/operator_1_agents.log"), + ); + default: + return pino({ level: process.env.LOGGER_LEVEL || "info" }); + } + + default: + return pino({ level: process.env.LOGGER_LEVEL || "info" }); + } +} + +// Cleanup function to close all file descriptors +export function cleanup() { + supervisorLogger.flush(); + registryLogger.flush(); + taskManagerLogger.flush(); + operatorLoggers.forEach((logger) => logger.flush()); +} + +// Handle process termination +process.on("beforeExit", cleanup); +process.on("SIGINT", () => { + cleanup(); + process.exit(0); +}); diff --git a/src/helpers/tmux-logger.ts b/src/helpers/tmux-logger.ts new file mode 100644 index 0000000..fe2bd56 --- /dev/null +++ b/src/helpers/tmux-logger.ts @@ -0,0 +1,105 @@ +import { pino, Logger } from "pino"; +import fs from "fs"; +import { AgentKind } from "src/agents/agent-registry.js"; + +// Ensure logs directory exists +if (!fs.existsSync("logs")) { + fs.mkdirSync("logs"); +} + +// Create separate log files for different components +const supervisorLogger: Logger = pino( + { + level: process.env.LOGGER_LEVEL || "info", + }, + pino.destination("logs/supervisor_agents.log"), +); + +const registryLogger: Logger = pino( + { + level: process.env.LOGGER_LEVEL || "info", + }, + pino.destination("logs/agent_registry.log"), +); + +const taskManagerLogger: Logger = pino( + { + level: process.env.LOGGER_LEVEL || "info", + }, + pino.destination("logs/task_manager.log"), +); + +// Cache for operator loggers +const operatorLoggers = new Map(); + +export enum LoggerType { + AGENT = "agent", + REGISTRY = "registry", + TASK_MANAGER = "taskManager", +} + +/** + * Get a logger instance for a specific component + * @param type - The type of logger to get + * @param operatorId - Optional operator ID for operator-specific loggers + * @returns A pino logger instance + */ +export function getLogger( + type: LoggerType, + agentKind?: AgentKind, + operatorId?: number | null, +): Logger { + switch (type) { + case "registry": + return registryLogger; + case "taskManager": + return taskManagerLogger; + case "agent": + switch (agentKind) { + case "supervisor": + return supervisorLogger; + case "operator": + // If operatorId is provided, create/get specific operator logger + if (operatorId !== null && operatorId !== undefined) { + if (!operatorLoggers.has(operatorId)) { + operatorLoggers.set( + operatorId, + pino( + { level: process.env.LOGGER_LEVEL || "info" }, + pino.destination(`logs/operator_${operatorId}_agents.log`), + ), + ); + } + return operatorLoggers.get(operatorId)!; + } + // Default operator logger for backward compatibility + return pino( + { level: process.env.LOGGER_LEVEL || "info" }, + pino.destination("logs/operator_1_agents.log"), + ); + default: + return pino({ level: process.env.LOGGER_LEVEL || "info" }); + } + break; + + default: + return pino({ level: process.env.LOGGER_LEVEL || "info" }); + } +} + +/** + * Cleanup function to close all file descriptors + */ +export function cleanup(): void { + supervisorLogger.flush(); + registryLogger.flush(); + taskManagerLogger.flush(); + operatorLoggers.forEach((logger) => logger.flush()); +} + +// Handle process termination +process.on("beforeExit", cleanup); +process.on("SIGINT", () => { + cleanup(); + process.exit(0); +}); diff --git a/src/tasks/task-manager.ts b/src/tasks/task-manager.ts index 47a8d38..afbaaa0 100644 --- a/src/tasks/task-manager.ts +++ b/src/tasks/task-manager.ts @@ -1,6 +1,8 @@ import { FrameworkError } from "bee-agent-framework"; import { Logger } from "bee-agent-framework/logger/logger"; import { AgentKindSchema } from "src/agents/agent-registry.js"; +import { getTaskStateLogger } from "src/tasks/task-state-logger.js"; +import { updateDeepPartialObject } from "src/utils/objects.js"; import { z } from "zod"; export const TaskConfigSchema = z @@ -64,6 +66,7 @@ export const TaskStatusEnumSchema = z.enum([ "STOPPED", "FAILED", "COMPLETED", + "REMOVED", ]); export type TaskStatusEnum = z.infer; @@ -81,7 +84,9 @@ export const TaskStatusSchema = z occupiedSince: z .date() .optional() + .nullable() .describe("Timestamp when the task was marked as occupied. undefined if not occupied"), + startRunAt: z.date().optional().describe("Timestamp of the execution start."), lastRunAt: z.date().optional().describe("Timestamp of the last successful execution"), nextRunAt: z.date().optional().describe("Expected timestamp of the next scheduled execution"), errorCount: z.number().int().describe("Count of consecutive execution failures"), @@ -92,6 +97,7 @@ export const TaskStatusSchema = z currentAgentId: z .string() .optional() + .nullable() .describe("ID of the agent currently operating on the task. undefined if not occupied"), completedRuns: z .number() @@ -131,6 +137,7 @@ export class TaskManager { intervalId: NodeJS.Timeout | null; } >(); + private removedTasks: Task[] = []; private scheduledTasksToStart: { taskId: string; agentId: string }[] = []; private taskStartIntervalId: NodeJS.Timeout | null = null; @@ -192,6 +199,7 @@ export class TaskManager { if (!task) { return; } + getTaskStateLogger().logHistoryEntry(taskId, entry); task.status.history.push(entry); @@ -318,6 +326,8 @@ export class TaskManager { history: [], }; + getTaskStateLogger().logStatusChange(status.id, status); + this.tasks.set(task.id, { id: task.id, intervalId: null, @@ -325,6 +335,8 @@ export class TaskManager { config: task, }); + getTaskStateLogger().logConfigCreate(task.id, task); + this.logger.info("Task scheduled successfully", { taskId: task.id }); } @@ -350,6 +362,7 @@ export class TaskManager { this.logger.error("Task not found", { taskId }); throw new Error(`Task ${taskId} not found`); } + const { status } = task; if (!this.hasOwnerPermission(taskId, agentId)) { this.logger.error("Permission denied for starting task", { taskId, agentId }); @@ -358,17 +371,18 @@ export class TaskManager { ); } - if (task.status.status === "RUNNING") { + if (status.status === "RUNNING") { this.logger.warn("Task already running", { taskId }); throw new Error(`Task ${taskId} is already running`); } - task.status.status = "RUNNING"; - task.status.nextRunAt = new Date(Date.now() + task.config.intervalMs); + this._updateTaskStatus(taskId, status, { + status: "RUNNING", + nextRunAt: new Date(Date.now() + task.config.intervalMs), + }); if (task.config.runImmediately) { this.logger.debug("Executing task immediately", { taskId }); - // TODO Parallelism await this.executeTask(taskId); } @@ -378,7 +392,12 @@ export class TaskManager { task.intervalId = setInterval(async () => { await self.executeTask(taskId); }, task.config.intervalMs); - task.status.status = "WAITING"; + + status.status = "WAITING"; + + this._updateTaskStatus(taskId, status, { + status: "WAITING", + }); } this.logger.info("Task started successfully", { taskId }); @@ -388,7 +407,7 @@ export class TaskManager { * Stops a task. * Only owners and admins can start/stop tasks. */ - stopTask(taskId: string, agentId: string): void { + stopTask(taskId: string, agentId: string, isCompleted = false): void { this.logger.info("Stopping task", { taskId, agentId }); if (!this.hasOwnerPermission(taskId, agentId)) { @@ -401,8 +420,9 @@ export class TaskManager { this.logger.error("Task not found", { taskId }); throw new Error(`Task ${taskId} not found`); } + const { status } = task; - if (task.status.status === "STOPPED") { + if (status.status === "STOPPED") { this.logger.debug("Task already stopped", { taskId }); return; } @@ -413,13 +433,15 @@ export class TaskManager { task.intervalId = null; } - if (task.status.isOccupied) { + if (status.isOccupied) { this.logger.debug("Releasing task occupancy before stop", { taskId }); this.releaseTaskOccupancy(taskId, agentId); } - task.status.status = "STOPPED"; - task.status.nextRunAt = undefined; + this._updateTaskStatus(taskId, status, { + status: isCompleted ? "COMPLETED" : "STOPPED", + nextRunAt: undefined, + }); this.logger.info("Task stopped successfully", { taskId }); } @@ -442,18 +464,21 @@ export class TaskManager { this.logger.error("Task not found", { taskId }); throw new Error(`Task ${taskId} not found`); } + const { status } = task; - if (task.status.status === "RUNNING") { + if (status.status === "RUNNING") { this.logger.debug("Stopping running task before removal", { taskId }); this.stopTask(taskId, agentId); } - if (task.status.isOccupied) { + if (status.isOccupied) { this.logger.debug("Releasing task occupancy before removal", { taskId }); this.releaseTaskOccupancy(taskId, agentId); } + this._updateTaskStatus(taskId, status, { status: "REMOVED" }); this.tasks.delete(taskId); + this.removedTasks.push(task); this.logger.info("Task removed successfully", { taskId }); } @@ -474,10 +499,13 @@ export class TaskManager { this.logger.debug("Task not available for occupancy", { taskId, exists: !!task }); return false; } + const { status } = task; - task.status.isOccupied = true; - task.status.occupiedSince = new Date(); - task.status.currentAgentId = agentId; + this._updateTaskStatus(taskId, status, { + isOccupied: true, + occupiedSince: new Date(), + currentAgentId: agentId, + }); if (this.options.occupancyTimeoutMs) { this.logger.debug("Setting occupancy timeout", { @@ -505,15 +533,18 @@ export class TaskManager { this.logger.debug("Task not available for release", { taskId, exists: !!task }); return false; } + const { status } = task; - if (task.status.currentAgentId !== agentId && !this.hasOwnerPermission(taskId, agentId)) { + if (status.currentAgentId !== agentId && !this.hasOwnerPermission(taskId, agentId)) { this.logger.error("Permission denied for releasing task occupancy", { taskId, agentId }); throw new PermissionError(`Agent ${agentId} cannot release occupancy of task ${taskId}`); } - task.status.isOccupied = false; - task.status.occupiedSince = undefined; - task.status.currentAgentId = undefined; + this._updateTaskStatus(taskId, status, { + isOccupied: false, + occupiedSince: null, + currentAgentId: null, + }); this.logger.info("Task occupancy released successfully", { taskId }); return true; @@ -546,18 +577,21 @@ export class TaskManager { updateTaskStatus( taskId: string, agentId: string, - update: Partial< - Pick - >, + update: Partial>, ) { this.logger.trace("Updating task status", { taskId, agentId, update }); const status = this.getTaskStatus(taskId, agentId); - status.errorCount = update.errorCount ?? status.errorCount; - status.completedRuns = update.completedRuns ?? status.completedRuns; - status.status = update.status ?? status.status; - status.lastRunAt = update.lastRunAt ?? status.lastRunAt; - status.nextRunAt = update.nextRunAt ?? status.nextRunAt; + return this._updateTaskStatus(taskId, status, update); + } + // Just for auditlog + private _updateTaskStatus( + taskId: string, + status: TaskStatus, + update: Partial, + ): TaskStatus { + updateDeepPartialObject(status, update); + getTaskStateLogger().logStatusChange(taskId, update); return status; } @@ -607,8 +641,9 @@ export class TaskManager { this.logger.warn("Task not found for execution", { taskId }); return; } + const { status } = task; - const retryAttempt = task.status.currentRetryAttempt; + const retryAttempt = status.currentRetryAttempt; if (retryAttempt > 0) { this.logger.debug("Retry attempt", { retryAttempt, maxRetries: task.config.maxRetries }); if (!!task.config.maxRetries && retryAttempt >= task.config.maxRetries) { @@ -616,26 +651,27 @@ export class TaskManager { } } - if (task.status.status === "COMPLETED" || task.status.isOccupied) { + if (status.status === "COMPLETED" || status.isOccupied) { this.logger.debug("Skipping task execution", { taskId, - reason: task.status.status === "COMPLETED" ? "completed" : "occupied", + reason: status.status === "COMPLETED" ? "completed" : "occupied", }); return; } const startTime = Date.now(); - task.status.lastRunAt = new Date(); - task.status.nextRunAt = new Date(Date.now() + task.config.intervalMs); + this._updateTaskStatus(taskId, status, { + lastRunAt: new Date(), + nextRunAt: new Date(Date.now() + task.config.intervalMs), + }); this.logger.debug("Executing task callback", { taskId, - lastRunAt: task.status.lastRunAt, - nextRunAt: task.status.nextRunAt, + lastRunAt: status.lastRunAt, + nextRunAt: status.nextRunAt, }); - // TODO Parallelism await this.onTaskStart(task.config, this, { onAgentCreate(taskId, agentId, taskManager) { taskManager.setTaskOccupied(taskId, agentId); @@ -655,7 +691,7 @@ export class TaskManager { maxRuns: task.config.maxRuns, retryAttempt: status.currentRetryAttempt, maxRetries: task.config.maxRetries, - agentId: task.status.currentAgentId, + agentId: task.status.currentAgentId!, executionTimeMs: Date.now() - startTime, }); @@ -668,7 +704,6 @@ export class TaskManager { taskManager.releaseTaskOccupancy(taskId, agentId); // Check if we've reached maxRuns if (task.config.maxRuns && task.status.completedRuns >= task.config.maxRuns) { - task.status.status = "COMPLETED"; taskManager.stopTask(taskId, task.config.ownerAgentId); taskManager.logger.info("Task reached maximum runs and has been stopped", { taskId, @@ -701,7 +736,7 @@ export class TaskManager { maxRuns: task.config.maxRuns, retryAttempt, maxRetries: task.config.maxRetries, - agentId: task.status.currentAgentId, + agentId: task.status.currentAgentId!, executionTimeMs: Date.now() - startTime, }); @@ -725,7 +760,9 @@ export class TaskManager { if (retryAttempt >= task.config.maxRetries) { taskManager.stopTask(taskId, task.config.ownerAgentId); } else { - status.currentRetryAttempt = retryAttempt + 1; + taskManager.updateTaskStatus(taskId, task.config.ownerAgentId, { + currentRetryAttempt: retryAttempt + 1, + }); } } }, diff --git a/src/tasks/task-state-logger.ts b/src/tasks/task-state-logger.ts new file mode 100644 index 0000000..9be2e1a --- /dev/null +++ b/src/tasks/task-state-logger.ts @@ -0,0 +1,56 @@ +import { TaskConfig, TaskHistoryEntry, TaskStatus } from "src/tasks/task-manager.js"; +import { BaseAuditLog } from "../base/audit-log.js"; + +export const DEFAULT_NAME = "task_state"; +export const DEFAULT_PATH = ["logs"] as readonly string[]; + +export enum TaskUpdateTypeEnum { + CONFIG = "config", + STATUS = "status", + HISTORY = "history", +} + +export type TaskStateData = TaskConfig | Partial | TaskHistoryEntry; + +class TaskStateLogger extends BaseAuditLog { + constructor(logPath?: string) { + super(DEFAULT_PATH, DEFAULT_NAME, logPath); + } + + public logConfigCreate(taskId: string, config: TaskConfig) { + this.logUpdate({ + timestamp: new Date().toISOString(), + type: TaskUpdateTypeEnum.CONFIG, + taskId, + data: config, + }); + } + + public logStatusChange(taskId: string, status: Partial) { + this.logUpdate({ + timestamp: new Date().toISOString(), + type: TaskUpdateTypeEnum.STATUS, + taskId, + data: status, + }); + } + + public logHistoryEntry(taskId: string, entry: TaskHistoryEntry) { + this.logUpdate({ + timestamp: new Date().toISOString(), + type: TaskUpdateTypeEnum.HISTORY, + taskId, + data: entry, + }); + } +} + +let instance: TaskStateLogger | null = null; + +// Export singleton instance +export const getTaskStateLogger = () => { + if (!instance) { + instance = new TaskStateLogger(); + } + return instance; +}; diff --git a/src/ui/task-monitor/monitor.ts b/src/ui/task-monitor/monitor.ts new file mode 100644 index 0000000..ce4cb6a --- /dev/null +++ b/src/ui/task-monitor/monitor.ts @@ -0,0 +1,464 @@ +import blessed from "blessed"; +import chokidar from "chokidar"; +import { createReadStream } from "fs"; +import { join } from "path"; +import { createInterface } from "readline"; +import { TaskStateData, TaskUpdateTypeEnum } from "src/tasks/task-state-logger.js"; +import { + TaskConfig, + TaskHistoryEntry, + TaskStatus, + TaskStatusEnum, +} from "src/tasks/task-manager.js"; +import { truncateText } from "src/utils/text.js"; +import { formatDuration } from "src/utils/time.js"; +import { + AMBIENT_VERSION, + applyAgentIdStyle, + applyBooleanStyle, + applyNumberStyle, + applyStatusStyle, + applyStyle, + UIConfig, +} from "./ui-config.js"; +import { stringToAgentId } from "src/agents/utils.js"; + +interface TaskUpdate { + timestamp: string; + type: TaskUpdateTypeEnum; + taskId: string; + data: TaskStateData; +} + +class TaskMonitor { + private screen: blessed.Widgets.Screen; + private taskList: blessed.Widgets.ListElement; + private taskConfig: blessed.Widgets.BoxElement; + private taskDetails: blessed.Widgets.BoxElement; + private taskHistory: blessed.Widgets.BoxElement; + private logBox: blessed.Widgets.Log; + private tasks = new Map(); + private taskConfigs = new Map(); + private selectedTaskIndex: number | null = null; + + constructor() { + this.screen = blessed.screen({ + smartCSR: true, + title: "Task Registry Monitor", + debug: true, + }); + + this.taskList = blessed.list({ + parent: this.screen, + width: "20%", + height: "70%", + left: 0, + top: 0, + border: { type: "line" }, + label: " Tasks ", + keys: true, + vi: true, + mouse: true, + style: { + selected: { bg: "blue", fg: "white" }, + border: { fg: "white" }, + item: { + hover: { bg: "blue" }, + }, + }, + tags: true, + scrollable: true, + scrollbar: { + ch: " ", + track: { + bg: "gray", + }, + style: { + inverse: true, + }, + }, + }); + + this.taskConfig = blessed.box({ + parent: this.screen, + width: "20%", + height: "70%", + left: "20%", + top: 0, + border: { type: "line" }, + label: " Config ", + content: "Select a task to view config", + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + alwaysScroll: true, + scrollbar: { + ch: " ", + track: { + bg: "gray", + }, + style: { + inverse: true, + }, + }, + }); + + this.taskDetails = blessed.box({ + parent: this.screen, + width: "60%", + height: "40%", + right: 0, + top: 0, + border: { type: "line" }, + label: " Details ", + content: "Select a task to view details", + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + alwaysScroll: true, + scrollbar: { + ch: " ", + track: { + bg: "gray", + }, + style: { + inverse: true, + }, + }, + }); + + this.taskHistory = blessed.box({ + parent: this.screen, + width: "60%", + height: "30%", + right: 0, + top: "40%", + border: { type: "line" }, + label: " History ", + content: "Select a task to view history", + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + alwaysScroll: true, + scrollbar: { + ch: " ", + track: { + bg: "gray", + }, + style: { + inverse: true, + }, + }, + }); + + this.logBox = blessed.log({ + parent: this.screen, + width: "100%", + height: "30%", + left: 0, + bottom: 0, + border: { type: "line" }, + label: " Live Updates ", + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: { + ch: " ", + track: { + bg: "gray", + }, + style: { + inverse: true, + }, + }, + }); + + this.screen.key(["escape", "q", "C-c"], () => process.exit(0)); + + const self = this; + // Handle task selection + this.taskList.on("select", (_, selectedIndex) => { + const item = this.taskList.getItem(selectedIndex); + self.selectedTaskIndex = selectedIndex; + if (item?.content) { + // Remove color tags to get clean taskId + const taskId = + item.content + .toString() + .replace(/\{[^}]+\}/g, "") + .split(" ") + .at(-1) ?? ""; + // this.logBox.log(`Selected task: ${taskId}`); + this.updateTaskDetails(taskId); + this.updateTaskConfig(taskId); // Add this line + } + }); + + // Enable mouse scrolling + this.taskList.on("mouse", (data) => { + if (data.action === "wheelup") { + this.taskList.scroll(-1); + this.screen.render(); + } else if (data.action === "wheeldown") { + this.taskList.scroll(1); + this.screen.render(); + } + }); + + this.taskDetails.on("mouse", (data) => { + if (data.action === "wheelup") { + this.taskDetails.scroll(-1); + this.screen.render(); + } else if (data.action === "wheeldown") { + this.taskDetails.scroll(1); + this.screen.render(); + } + }); + + this.screen.render(); + } + + private updateTaskList(shouldRender = true): void { + const items = Array.from(this.tasks.values()).map( + (task) => `${applyStatusStyle(task.status, task.id)}`, + ); + + this.taskList.setItems(items); + if (shouldRender) { + this.screen.render(); + } + } + + private updateTaskDetails(taskId: string, shouldRender = true): void { + const task = this.tasks.get(taskId); + if (!task) { + // this.logBox.log(`Task not found: ${taskId}`); + return; + } + + const details = [ + `{bold}Status:{/bold} ${applyStatusStyle(task.status)}{/}`, + `{bold}Is Occupied:{/bold} ${applyBooleanStyle(task.isOccupied)}`, + task.currentAgentId + ? `{bold}Current Agent:{/bold} ${applyAgentIdStyle(stringToAgentId(task.currentAgentId))}` + : null, + `{bold}Owner:{/bold} ${applyAgentIdStyle(stringToAgentId(task.ownerAgentId))}`, + `{bold}Completed Runs:{/bold} ${applyNumberStyle(task.completedRuns)}`, + `{bold}Error Count:{/bold} ${applyNumberStyle(task.errorCount, true)}`, + task.lastRunAt + ? `{bold}Last Run:{/bold} ${applyStyle(new Date(task.lastRunAt).toLocaleString(), UIConfig.labels.timestamp)}` + : null, + task.nextRunAt + ? `{bold}Next Run:{/bold} ${applyStyle(new Date(task.nextRunAt).toLocaleString(), UIConfig.labels.timestamp)}` + : null, + "", + task.history.at(-1)?.output + ? `{bold}Output:{/bold}\n${applyStyle(String(task.history.at(-1)?.output), UIConfig.labels.output)}` + : null, + "", + "", + task.history.at(-1)?.error + ? `{bold}Error:{/bold}\n${applyStyle(String(task.history.at(-1)?.error), UIConfig.labels.error)}` + : null, + "", + ] + .filter((line) => line !== null) + .join("\n"); + + this.taskDetails.setContent(details); + + const history = [ + ...task.history + .slice(-30) + .map( + (entry) => + `${applyStyle(new Date(entry.timestamp).toLocaleString(), UIConfig.labels.timestamp)}` + + ` ${applyStatusStyle(entry.terminalStatus)}` + + ` ${applyAgentIdStyle(stringToAgentId(String(entry.agentId)))}` + + ` ${applyStyle(formatDuration(entry.executionTimeMs), UIConfig.labels.executionTime)}` + + (entry.output + ? ` ${applyStyle(truncateText(String(entry.output), 512), UIConfig.labels.output, AMBIENT_VERSION)}` + : "") + + (entry.error + ? ` ${applyStyle(truncateText(entry.error, 512), UIConfig.labels.error)}` + : ""), + ), + ] + .filter((line) => line !== null) + .join("\n"); + this.taskHistory.setContent(history); + + // this.logBox.log(`Updated details for task: ${taskId}`); + if (shouldRender) { + this.screen.render(); + } + } + + private updateTaskConfig(taskId: string, shouldRender = true): void { + const config = this.taskConfigs.get(taskId); + if (!config) { + this.taskConfig.setContent("No configuration available"); + return; + } + + const details = [ + `{bold}Task ID:{/bold} ${applyStyle(config.id, UIConfig.labels.taskId)}`, + `{bold}Agent Kind:{/bold} ${applyStyle(config.agentKind, UIConfig.labels.agentKind)}`, + `{bold}Agent Type:{/bold} ${applyStyle(config.agentType, UIConfig.labels.agentType)}`, + `{bold}Owner:{/bold} ${applyAgentIdStyle(stringToAgentId(config.ownerAgentId))}`, + "", + "{bold}Execution Settings:{/bold}", + `{bold}Interval:{/bold} ${applyStyle(formatDuration(config.intervalMs), UIConfig.labels.timestamp)}`, + `{bold}Run Immediately:{/bold} ${applyBooleanStyle(config.runImmediately)}`, + config.maxRuns + ? `{bold}Max Runs:{/bold} ${applyStyle(String(config.maxRuns), UIConfig.labels.timestamp)}` + : null, + config.maxRetries + ? `{bold}Max Retries:{/bold} ${applyStyle(String(config.maxRetries), UIConfig.labels.timestamp)}` + : null, + config.retryDelayMs + ? `{bold}Retry Delay:{/bold} ${applyStyle(formatDuration(config.retryDelayMs), UIConfig.labels.timestamp)}` + : null, + "", + "{bold}Description:{/bold}", + applyStyle(config.description, UIConfig.labels.description), + "", + "{bold}Input:{/bold}", + applyStyle(config.input, UIConfig.labels.input), + ] + .filter((line) => line !== null) + .join("\n"); + + this.taskConfig.setContent(details); + if (shouldRender) { + this.screen.render(); + } + } + + private processLogLine(line: string, shouldRender = true): void { + try { + const update: TaskUpdate = JSON.parse(line); + const task = this.tasks.get(update.taskId) || { + id: update.taskId, + status: "SCHEDULED" as TaskStatusEnum, + isOccupied: false, + errorCount: 0, + currentRetryAttempt: 0, + ownerAgentId: "", + completedRuns: 0, + history: [], + }; + + if (update.type === TaskUpdateTypeEnum.CONFIG) { + this.taskConfigs.set(update.taskId, update.data as TaskConfig); + } else if (update.type === TaskUpdateTypeEnum.STATUS) { + Object.assign(task, update.data); + } else if (update.type === TaskUpdateTypeEnum.HISTORY) { + task.history.push(update.data as TaskHistoryEntry); + } + + this.tasks.set(update.taskId, task); + this.updateTaskList(shouldRender); + + // Update details if this task is currently selected + const selectedIndex = this.selectedTaskIndex; + if (typeof selectedIndex === "number") { + const selectedItem = this.taskList.getItem(selectedIndex); + if (selectedItem?.content) { + const selectedTaskId = selectedItem.content.toString().replace(/\{[^}]+\}/g, ""); + if (selectedTaskId === update.taskId) { + this.updateTaskDetails(update.taskId, shouldRender); + } + } + } + + this.logBox.log( + `${new Date().toLocaleString()} - Task ${update.taskId}: ${update.type} update ${JSON.stringify(update.data)}`, + ); + if (shouldRender) { + this.screen.render(); + } + } catch (error) { + if (error instanceof Error) { + this.logBox.log(`Error processing log line: ${error.message}`); + } else { + this.logBox.log("Unknown error processing log line"); + } + } + } + + private async initializeStateFromLog(logPath: string): Promise { + try { + this.logBox.log("Reading initial state from log..."); + + const rl = createInterface({ + input: createReadStream(logPath, { encoding: "utf8" }), + crlfDelay: Infinity, + }); + + for await (const line of rl) { + this.processLogLine(line, false); // Don't render updates while initializing + } + + this.logBox.log(`Initial state loaded: ${this.tasks.size} tasks found`); + this.updateTaskList(); + this.screen.render(); + } catch (error) { + if (error instanceof Error) { + this.logBox.log(`Error reading initial state: ${error.message}`); + } else { + this.logBox.log("Unknown error reading initial state"); + } + } + } + + private watchLogFile(logPath: string): void { + let lastProcessedSize = 0; + + chokidar + .watch(logPath, { + persistent: true, + usePolling: true, + interval: 100, + }) + .on("change", async (path) => { + try { + const rl = createInterface({ + input: createReadStream(path, { encoding: "utf8", start: lastProcessedSize }), + crlfDelay: Infinity, + }); + + for await (const line of rl) { + this.processLogLine(line, true); // Render updates for new changes + lastProcessedSize += Buffer.from(line).length + 1; // +1 for newline + } + } catch (error) { + if (error instanceof Error) { + this.logBox.log(`Error processing log update: ${error.message}`); + } + } + }); + } + + public async start(): Promise { + const logPath = join(process.cwd(), "logs", "task_state.log"); + + // First read the entire log to build initial state + await this.initializeStateFromLog(logPath); + + // Then start watching for changes + this.watchLogFile(logPath); + } +} + +// Start the monitor +const monitor = new TaskMonitor(); +monitor.start(); diff --git a/src/ui/task-monitor/ui-config.ts b/src/ui/task-monitor/ui-config.ts new file mode 100644 index 0000000..e12973c --- /dev/null +++ b/src/ui/task-monitor/ui-config.ts @@ -0,0 +1,120 @@ +import { AgentKind, AgentKindSchema } from "src/agents/agent-registry.js"; +import { AgentId } from "src/agents/utils.js"; +import { TaskStatusEnum } from "src/tasks/task-manager.js"; + +export interface StyleItem { + fg?: string; + bold?: boolean; + icon?: string; +} +export type StyleItemVersioned = Record; +export type StyleItemValue = StyleItem | StyleItemVersioned; + +export type StyleCategory = Record; + +export const DEFAULT_VERSION = "default"; +export const AMBIENT_VERSION = "ambient"; + +export const UIConfig = { + labels: { + taskId: { fg: "#CC5500", bold: true }, + status: { fg: "white", bold: true }, + agentKind: { fg: "magenta", bold: true }, + agentType: { fg: "cyan", bold: true }, + agentId: { + [AgentKindSchema.Values.supervisor]: { fg: "#8B4513", bold: true, icon: "⬢" }, + [AgentKindSchema.Values.operator]: { fg: "#8B4513", bold: true, icon: "⬡" }, + }, + owner: { fg: "#8B4513", bold: true }, + description: { fg: "#7393B3", bold: false }, + input: { fg: "yellow", bold: false }, + output: { + [DEFAULT_VERSION]: { fg: "green", bold: false }, + [AMBIENT_VERSION]: { fg: "#2E8B57", bold: false }, + }, + error: { fg: "red", bold: true }, + executionTime: { fg: "yellow" }, + timestamp: { fg: "gray" }, + } satisfies StyleCategory, + + status: { + RUNNING: { fg: "green", icon: "▶" }, // Play triangle + FAILED: { fg: "red", icon: "■" }, // Square + COMPLETED: { fg: "blue", icon: "●" }, // Circle + SCHEDULED: { fg: "yellow", icon: "◆" }, // Diamond + WAITING: { fg: "cyan", icon: "◇" }, // Hollow diamond + STOPPED: { fg: "grey", icon: "◼" }, // Filled square + REMOVED: { fg: "darkgray", icon: "×" }, // Cross + } satisfies StyleCategory, + + boolean: { + TRUE: { fg: "green", icon: "[✓]" }, + FALSE: { fg: "red", icon: "[✕]" }, + } satisfies StyleCategory, + + borders: { + type: "line", + fg: "white", + }, + + scrollbar: { + track: { bg: "gray" }, + style: { inverse: true }, + }, + number: { + positive: { fg: "green", bold: true }, + neutral: { fg: "grey", bold: false }, + negative: { fg: "red", bold: true }, + } satisfies StyleCategory, +}; + +export const applyStyle = ( + text: string, + styleItem: StyleItem | StyleItemVersioned, + version = "default", +) => { + let style; + // = version ? Object.keys(styleItem).includes(version) ? styleItem[version] : + if (version && Object.keys(styleItem).includes(version)) { + style = (styleItem as StyleItemVersioned)[version]; + } else { + style = styleItem; + } + + let styled = text; + if (style.fg) { + styled = `{${style.fg}-fg}${styled}{/${style.fg}-fg}`; + } + if (style.bold) { + styled = `{bold}${styled}{/bold}`; + } + return styled; +}; + +export function applyStatusStyle(status: TaskStatusEnum, value?: string) { + const { fg, icon } = UIConfig.status[status]; + return applyStyle(`${icon} ${value ?? status}`, { ...UIConfig.labels.status, fg }); +} + +export function applyNumberStyle(count: number, inverse = false) { + let style; + if (count === 0) { + style = UIConfig.number.neutral; + } else if (count > 0) { + style = inverse ? UIConfig.number.negative : UIConfig.number.positive; + } else { + style = inverse ? UIConfig.number.positive : UIConfig.number.negative; + } + + return applyStyle(String(count), style); +} + +export function applyBooleanStyle(value: boolean) { + const style = value ? UIConfig.boolean.TRUE : UIConfig.boolean.FALSE; + return applyStyle(style.icon, { fg: style.fg }); +} + +export function applyAgentIdStyle(agentId: AgentId) { + const style = UIConfig.labels.agentId[agentId.agentKind as AgentKind]; + return applyStyle(`${style.icon} ${agentId.agentType}[${agentId.num}]`, { ...style }); +} diff --git a/src/utils/objects.ts b/src/utils/objects.ts new file mode 100644 index 0000000..191fe94 --- /dev/null +++ b/src/utils/objects.ts @@ -0,0 +1,34 @@ +type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; +}; + +/** + * Recursively updates original object with values from update object + * @param original The original object to update + * @param update Partial object containing updates + * @returns The updated original object + */ +export function updateDeepPartialObject(original: T, update: DeepPartial): T { + const keys = Object.keys(update) as (keyof T)[]; + + for (const key of keys) { + const updateValue = update[key]; + + // Handle nested objects recursively + if ( + updateValue !== null && + typeof updateValue === "object" && + !Array.isArray(updateValue) && + typeof original[key] === "object" + ) { + original[key] = { + ...(original[key] as object), + ...updateDeepPartialObject(original[key] as object, updateValue as DeepPartial), + } as T[keyof T]; + } else { + original[key] = updateValue as T[keyof T]; + } + } + + return original; +} diff --git a/src/utils/text.ts b/src/utils/text.ts new file mode 100644 index 0000000..2ae0c8e --- /dev/null +++ b/src/utils/text.ts @@ -0,0 +1,6 @@ +export function truncateText(text: string, maxLength: number): string { + if (text.length <= maxLength) { + return text; + } + return text.substring(0, maxLength - 3) + "..."; +} diff --git a/src/utils/time.ts b/src/utils/time.ts new file mode 100644 index 0000000..c0d0222 --- /dev/null +++ b/src/utils/time.ts @@ -0,0 +1,17 @@ +export function formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) { + return `${days}d ${hours % 24}h`; + } + if (hours > 0) { + return `${hours}h ${minutes % 60}m`; + } + if (minutes > 0) { + return `${minutes}m ${seconds % 60}s`; + } + return `${seconds}s`; +} diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..0c7dfd9 --- /dev/null +++ b/start.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# Default number of operators if not specified +OPERATORS_COUNT=${1:-2} + +# Check if tmux is installed +if ! command -v tmux &> /dev/null; then + echo "tmux is not installed. Please install it first." + exit 1 +fi + +# Kill existing session if it exists +tmux kill-session -t agent_system 2>/dev/null + +# Create logs directory if it doesn't exist +mkdir -p logs + +# Start a new session +tmux new-session -d -s agent_system -n 'Agent System' + +# First split horizontally at 66% for the top section +tmux split-window -v -p 34 + +# Now work on the top section (pane 0) +# Split top section vertically +tmux select-pane -t 0 +tmux split-window -h -p 50 + +# Split top-left pane horizontally +tmux select-pane -t 0 +tmux split-window -v -p 50 + +# Split top-right pane horizontally +tmux select-pane -t 2 +tmux split-window -v -p 50 + +# Now we have our 2x2 grid in the top section: +# 0: Interactive (top-left) +# 1: Supervisor Log (bottom-left) +# 2: Agent Registry (top-right) +# 3: Task Manager (bottom-right) + +# Configure the top section panes +tmux select-pane -t 0 +tmux send-keys 'echo "🤖 Starting supervisor interaction..." && sleep 2 && NODE_ENV=development node start' C-m + +tmux select-pane -t 1 +tmux send-keys 'tail -f logs/supervisor_agents.log | pino-pretty' C-m + +tmux select-pane -t 2 +tmux send-keys 'tail -f logs/agent_registry.log | pino-pretty' C-m + +tmux select-pane -t 3 +tmux send-keys 'tail -f logs/task_manager.log | pino-pretty' C-m + +# Now handle the bottom section for operators +# Split the bottom pane horizontally for each operator +tmux select-pane -t 4 +for ((i=1; i Date: Sat, 8 Feb 2025 03:09:11 +0100 Subject: [PATCH 02/13] feat(agent monitor): add pools and agent template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- package.json | 5 +- src/agent.ts | 55 +- src/agents/agent-registry.ts | 94 ++- src/agents/agent-state-logger.ts | 99 +++ src/agents/utils.ts | 12 +- src/base/audit-log.ts | 18 +- src/tasks/task-manager.ts | 24 +- src/tasks/task-state-logger.ts | 11 +- src/ui/agent-monitor.ts | 579 ++++++++++++++++++ .../monitor.ts => task-monitor.ts} | 168 ++--- src/ui/{task-monitor => }/ui-config.ts | 84 ++- 11 files changed, 1002 insertions(+), 147 deletions(-) create mode 100644 src/agents/agent-state-logger.ts create mode 100644 src/ui/agent-monitor.ts rename src/ui/{task-monitor/monitor.ts => task-monitor.ts} (80%) rename src/ui/{task-monitor => }/ui-config.ts (63%) diff --git a/package.json b/package.json index 6a774fb..48bd202 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,9 @@ "scripts": { "start": "NODE_ENV=development npm run startx", "startx": "chmod +x ./start.sh && ./start.sh", - "start:dev": "tsx --inspect --no-warnings src/agent.js", - "task:monitor": "tsx --inspect --no-warnings src/ui/task-monitor/monitor.js", + "start:dev": "tsx --no-warnings src/agent.js", + "task:monitor": "tsx --inspect --no-warnings src/ui/task-monitor.js", + "agent:monitor": "tsx --inspect --no-warnings src/ui/agent-monitor.js", "ts:check": "tsc --noEmit --project tsconfig.json", "build": "rimraf dist && tsc", "lint": "eslint", diff --git a/src/agent.ts b/src/agent.ts index 824fcf5..9795344 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -10,33 +10,44 @@ import { createConsoleReader } from "./helpers/reader.js"; import { TaskManager } from "./tasks/task-manager.js"; import { getLogger, LoggerType } from "./helpers/tmux-logger.js"; import { agentIdToString } from "./agents/utils.js"; +import { getAgentStateLogger } from "./agents/agent-state-logger.js"; +import { getTaskStateLogger } from "./tasks/task-state-logger.js"; + +// Reset audit logs +getAgentStateLogger(); +getTaskStateLogger(); const registry = new AgentRegistry({ - async onCreate( - config, - poolStats, - toolsFactory, - ): Promise<{ agentId: string; instance: BeeAgent }> { - const { kind: agentKind, type: agentType, instructions, description } = config; - const num = poolStats.created + 1; - const agentId = agentIdToString({ agentKind, agentType, num }); - const tools = config.tools == null ? toolsFactory.getAvailableToolsNames() : config.tools; - const instance = createAgent( - { - agentKind, - agentType, - agentId, - description, - instructions, - tools, - }, + agentLifecycle: { + async onCreate( + config, + poolStats, toolsFactory, - ); + ): Promise<{ agentId: string; instance: BeeAgent }> { + const { kind: agentKind, type: agentType, instructions, description } = config; + const num = poolStats.created + 1; + const agentId = agentIdToString({ agentKind, agentType, num }); + const tools = config.tools == null ? toolsFactory.getAvailableToolsNames() : config.tools; + const instance = createAgent( + { + agentKind, + agentType, + agentId, + description, + instructions, + tools, + }, + toolsFactory, + ); - return { agentId, instance }; + return { agentId, instance }; + }, + async onDestroy(instance) { + instance.destroy(); + }, }, - async onDestroy(instance) { - instance.destroy(); + onAgentTypeRegistered(agentKind, agentType) { + taskManager.registerAgentType(agentKind, agentType); }, }); diff --git a/src/agents/agent-registry.ts b/src/agents/agent-registry.ts index 51093b5..5428c60 100644 --- a/src/agents/agent-registry.ts +++ b/src/agents/agent-registry.ts @@ -1,6 +1,7 @@ import { Logger } from "bee-agent-framework/logger/logger"; import { BaseToolsFactory } from "src/base/tools-factory.js"; import { z } from "zod"; +import { getAgentStateLogger } from "./agent-state-logger.js"; export const AgentKindSchema = z .enum(["supervisor", "operator"]) @@ -138,6 +139,8 @@ export interface AgentInstanceRef { instance: TInstance; } +export type AgentTypesMap = Map>; + /** * Registry for managing agent types, instances, and pools. * Provides functionality for: @@ -159,18 +162,26 @@ export class AgentRegistry { /** Map of agent pools by kind and type, containing sets of available agent IDs */ private agentPools: Map; /** Callbacks for agent lifecycle events */ - private callbacks: AgentLifecycleCallbacks; + private lifecycleCallbacks: AgentLifecycleCallbacks; + private onAgentTypeRegistered: (agentKind: AgentKind, agentType: string) => void; /** Maps of tools factories for use by agents per agent kinds */ private toolsFactory = new Map(); /** * Creates a new AgentRegistry instance - * @param callbacks - Callbacks for handling agent lifecycle events + * @param callbacks - Callbacks for handling agent lifecycle events and agent type registration */ - constructor(callbacks: AgentLifecycleCallbacks) { + constructor({ + agentLifecycle, + onAgentTypeRegistered, + }: { + onAgentTypeRegistered: (agentKind: AgentKind, agentType: string) => void; + agentLifecycle: AgentLifecycleCallbacks; + }) { this.logger = Logger.root.child({ name: "AgentRegistry" }); this.logger.info("Initializing AgentRegistry"); - this.callbacks = callbacks; + this.lifecycleCallbacks = agentLifecycle; + this.onAgentTypeRegistered = onAgentTypeRegistered; // Initialize agent pools for all agent kinds this.agentConfigs = new Map(AgentKindSchema.options.map((kind) => [kind, new Map()])); this.agentPools = new Map(AgentKindSchema.options.map((kind) => [kind, new Map()])); @@ -181,7 +192,10 @@ export class AgentRegistry { * @param tuples */ registerToolsFactories(tuples: [AgentKind, BaseToolsFactory][]) { - tuples.map(([kind, factory]) => this.toolsFactory.set(kind, factory)); + tuples.map(([kind, factory]) => { + this.toolsFactory.set(kind, factory); + getAgentStateLogger().logAvailableTools(kind, factory.getAvailableTools()); + }); } private getAgentKindPoolMap(kind: AgentKind) { @@ -261,6 +275,7 @@ export class AgentRegistry { } agentTypesMap.set(type, config); + getAgentStateLogger().logAgentConfigCreate(config); // Initialize pool if pooling is enabled if (maxPoolSize > 0) { @@ -280,6 +295,8 @@ export class AgentRegistry { }); } } + + this.onAgentTypeRegistered(kind, type); } /** @@ -355,7 +372,7 @@ export class AgentRegistry { if (!pool || config.maxPoolSize === 0) { this.logger.debug("No pool available, creating new agent", { type }); - return this.createAgent(kind, type, false); + return this._acquireAgent(await this.createAgent(kind, type, false)); } // Try to get an available agent from the pool @@ -366,13 +383,7 @@ export class AgentRegistry { agent.inUse = true; this.logger.debug("Acquired agent from pool", { type, agentId }); - - if (this.callbacks.onAcquire) { - this.logger.trace("Executing onAcquire callback", { agentId }); - await this.callbacks.onAcquire(agentId); - } - - return agent as AgentWithInstance; + return this._acquireAgent(agent); } } @@ -384,7 +395,7 @@ export class AgentRegistry { currentSize: pool.size, maxSize: config.maxPoolSize, }); - return this.createAgent(kind, type, false); + return this._acquireAgent(await this.createAgent(kind, type, false)); } this.logger.error("No available agents and pool at capacity", { @@ -394,6 +405,23 @@ export class AgentRegistry { throw new Error(`No available agents of type '${type}' in pool and pool is at capacity`); } + private async _acquireAgent(agent: Agent) { + const { agentId } = agent; + if (this.lifecycleCallbacks.onAcquire) { + this.logger.trace("Executing onAcquire callback", { agentId }); + await this.lifecycleCallbacks.onAcquire(agentId); + } + getAgentStateLogger().logAgentLifeCycle({ event: "onAcquire", agentId: agent.agentId }); + + const poolStats = this.getPoolStats(agent.kind, agent.type); + getAgentStateLogger().logPoolChange({ + agentKind: agent.kind, + agentType: agent.type, + ...poolStats, + }); + return agent as AgentWithInstance; + } + /** * Releases an agent back to its pool or destroys it * @param agentId - ID of the agent to release @@ -416,15 +444,23 @@ export class AgentRegistry { return; } - if (this.callbacks.onRelease) { + if (this.lifecycleCallbacks.onRelease) { this.logger.trace("Executing onRelease callback", { agentId }); - await this.callbacks.onRelease(agentId); + await this.lifecycleCallbacks.onRelease(agentId); } // Return to pool agent.inUse = false; pool.add(agentId); this.logger.debug("Agent released back to pool", { agentId, type: agent.type }); + getAgentStateLogger().logAgentLifeCycle({ event: "onRelease", agentId: agent.agentId }); + + const poolStats = this.getPoolStats(agent.kind, agent.type); + getAgentStateLogger().logPoolChange({ + agentKind: agent.kind, + agentType: agent.type, + ...poolStats, + }); } /** @@ -441,9 +477,13 @@ export class AgentRegistry { ): Promise> { this.logger.debug("Creating new agent", { kind, type, forPool }); const config = this.getAgentTypeConfig(kind, type); - const poolStats = this.getPoolStats(kind, type); + let poolStats = this.getPoolStats(kind, type); const toolsFactory = this.getToolsFactory(kind); - const { agentId, instance } = await this.callbacks.onCreate(config, poolStats, toolsFactory); + const { agentId, instance } = await this.lifecycleCallbacks.onCreate( + config, + poolStats, + toolsFactory, + ); const agent = { agentId, @@ -456,6 +496,11 @@ export class AgentRegistry { this.agents.set(agentId, agent); this.logger.info("Agent created successfully", { agentId, type, forPool }); + + getAgentStateLogger().logAgentLifeCycle({ event: "onCreate", agentId: agent.agentId }); + + poolStats = this.getPoolStats(kind, type); + getAgentStateLogger().logPoolChange({ agentKind: kind, agentType: type, ...poolStats }); return agent; } @@ -477,15 +522,26 @@ export class AgentRegistry { if (pool) { pool.delete(agentId); this.logger.trace("Removed agent from pool", { agentId, kind: agent.kind, type: agent.type }); + } else { + throw new Error(`Missing pool`); } - await this.callbacks.onDestroy(agent.instance); + await this.lifecycleCallbacks.onDestroy(agent.instance); this.agents.delete(agentId); this.logger.info("Agent destroyed successfully", { agentId, kind: agent.kind, type: agent.type, }); + + getAgentStateLogger().logAgentLifeCycle({ event: "onDestroy", agentId }); + + const poolStats = this.getPoolStats(agent.kind, agent.type); + getAgentStateLogger().logPoolChange({ + agentKind: agent.kind, + agentType: agent.type, + ...poolStats, + }); } /** diff --git a/src/agents/agent-state-logger.ts b/src/agents/agent-state-logger.ts new file mode 100644 index 0000000..ee96f1a --- /dev/null +++ b/src/agents/agent-state-logger.ts @@ -0,0 +1,99 @@ +import { BaseAuditLog, LogUpdate } from "../base/audit-log.js"; +import { AgentConfig, AgentKind, AvailableTool } from "./agent-registry.js"; + +export const DEFAULT_NAME = "agent_state"; +export const DEFAULT_PATH = ["logs"] as readonly string[]; + +export enum AgentUpdateTypeEnum { + AVAILABLE_TOOLS = "available_tools", + AGENT_CONFIG = "agent_config", + POOL = "pool", + AGENT = "agent", +} + +export interface AvailableToolsData { + agentKind: AgentKind; + availableTools?: AvailableTool[]; +} + +export interface PoolChangeData { + agentKind: AgentKind; + agentType: string; + available: number; + poolSize: number; + inUse: number; + created: number; +} + +export interface AgentLifecycleData { + event: "onCreate" | "onDestroy" | "onAcquire" | "onRelease"; + agentId: string; +} + +export type AgentStateData = AgentConfig | AvailableToolsData | PoolChangeData | AgentLifecycleData; + +export interface AgentStateUpdate extends LogUpdate {} + +class AgentStateLogger extends BaseAuditLog { + constructor(logPath?: string) { + super(DEFAULT_PATH, DEFAULT_NAME, logPath); + } + + public logAgentConfigCreate(config: AgentConfig) { + this.logUpdate({ + type: AgentUpdateTypeEnum.AGENT_CONFIG, + data: config, + }); + } + + public logPoolChange(data: PoolChangeData) { + this.logUpdate({ + type: AgentUpdateTypeEnum.POOL, + data, + }); + } + + // public logStatusChange(agentId: string, status: Partial) { + // this.logUpdate({ + // timestamp: new Date().toISOString(), + // type: AgentUpdateTypeEnum.STATUS, + // agentId, + // data: status, + // }); + // } + + // public logAgentDestroyed(agentId: string) { + // this.logUpdate({ + // timestamp: new Date().toISOString(), + // type: AgentUpdateTypeEnum.DESTROYED, + // agentId, + // data: undefined, + // }); + // } + + public logAvailableTools(agentKind: AgentKind, availableTools: AvailableTool[]) { + this.logUpdate({ + type: AgentUpdateTypeEnum.AVAILABLE_TOOLS, + data: { + agentKind, + availableTools, + }, + }); + } + + public logAgentLifeCycle(data: AgentLifecycleData) { + this.logUpdate({ + type: AgentUpdateTypeEnum.AGENT, + data, + }); + } +} + +let instance: AgentStateLogger | null = null; + +export const getAgentStateLogger = () => { + if (!instance) { + instance = new AgentStateLogger(); + } + return instance; +}; diff --git a/src/agents/utils.ts b/src/agents/utils.ts index b4d9be1..b2c8450 100644 --- a/src/agents/utils.ts +++ b/src/agents/utils.ts @@ -1,11 +1,15 @@ +import { AgentKind } from "./agent-registry.js"; + +export const UNDEFINED_NUM = -1; + export interface AgentId { - agentKind: string; + agentKind: AgentKind; agentType: string; - num: number; + num?: number; } export function agentIdToString(agentId: AgentId) { - return `${agentId.agentKind}:${agentId.agentType}[${agentId.num}]`; + return `${agentId.agentKind}:${agentId.agentType}[${agentId.num ?? UNDEFINED_NUM}]`; } export function stringToAgentId(agentId: string): AgentId { @@ -13,7 +17,7 @@ export function stringToAgentId(agentId: string): AgentId { const match = rest.match(/([^[]+)(?:\[(\d+)\])?/); return { - agentKind: kind, + agentKind: kind as AgentKind, agentType: match?.[1] ?? "", num: match?.[2] ? parseInt(match[2]) : -1, }; diff --git a/src/base/audit-log.ts b/src/base/audit-log.ts index 0b44565..bde88f5 100644 --- a/src/base/audit-log.ts +++ b/src/base/audit-log.ts @@ -4,11 +4,15 @@ import { join } from "path"; export interface LogUpdate { timestamp: string; type: TType; - taskId: string; data: TData; } -export class BaseAuditLog { +export interface LogInit { + timestamp: string; + type: "@log_init"; +} + +export class BaseAuditLog> { protected logPath: string; constructor(logFileDefaultPath: readonly string[], logFileDefaultName: string, logPath?: string) { @@ -18,6 +22,7 @@ export class BaseAuditLog { this.logPath = logPath; } this.rotateLogFileIfExists(); + this.logInit(); } private rotateLogFileIfExists(): void { @@ -39,7 +44,12 @@ export class BaseAuditLog { } } - protected logUpdate(update: LogUpdate) { - appendFileSync(this.logPath, JSON.stringify(update) + "\n"); + private logInit() { + this.logUpdate({ type: "@log_init" }); + } + + protected logUpdate(update: Omit | Omit) { + const timestamp = new Date().toISOString(); + appendFileSync(this.logPath, JSON.stringify({ ...update, timestamp }) + "\n"); } } diff --git a/src/tasks/task-manager.ts b/src/tasks/task-manager.ts index afbaaa0..d77a39e 100644 --- a/src/tasks/task-manager.ts +++ b/src/tasks/task-manager.ts @@ -1,6 +1,6 @@ import { FrameworkError } from "bee-agent-framework"; import { Logger } from "bee-agent-framework/logger/logger"; -import { AgentKindSchema } from "src/agents/agent-registry.js"; +import { AgentKind, AgentKindSchema } from "src/agents/agent-registry.js"; import { getTaskStateLogger } from "src/tasks/task-state-logger.js"; import { updateDeepPartialObject } from "src/utils/objects.js"; import { z } from "zod"; @@ -52,7 +52,7 @@ export const TaskHistoryEntrySchema = z "Maximum number of retry attempts if task execution fails. undefined if no retries.", ) .nullish(), - agentId: z.string().optional().describe("ID of agent that executed the task, if occupied"), + agentId: z.string().nullable().describe("ID of agent that executed the task, if occupied"), executionTimeMs: z.number().describe("How long the task execution took in milliseconds"), }) .describe("Records details about a single execution of a task"); @@ -140,6 +140,7 @@ export class TaskManager { private removedTasks: Task[] = []; private scheduledTasksToStart: { taskId: string; agentId: string }[] = []; private taskStartIntervalId: NodeJS.Timeout | null = null; + private registeredAgentTypes = new Map(); constructor( private onTaskStart: ( @@ -190,6 +191,16 @@ export class TaskManager { }, 100); // Runs every 100ms (0.1 second) } + registerAgentType(agentKind: AgentKind, agentType: string): void { + let types = this.registeredAgentTypes.get(agentKind); + if (!types) { + types = [agentType]; + this.registeredAgentTypes.set(agentKind, types); + } else { + types.push(agentType); + } + } + /** * Add a history entry for a task * @private @@ -199,9 +210,9 @@ export class TaskManager { if (!task) { return; } - getTaskStateLogger().logHistoryEntry(taskId, entry); task.status.history.push(entry); + getTaskStateLogger().logHistoryEntry(taskId, entry); // Trim history if it exceeds maximum entries const maxEntries = task.status.maxHistoryEntries ?? this.options.maxHistoryEntries; @@ -310,6 +321,13 @@ export class TaskManager { ); } + const types = this.registeredAgentTypes.get(task.agentKind); + if (!types || !types.includes(task.agentType)) { + throw new Error( + `Unregistered agent type for task.agentKind:${task.agentKind} task.agentType: ${task.agentType}`, + ); + } + if (this.tasks.has(task.id)) { this.logger.error("Task already exists", { taskId: task.id }); throw new Error(`Task with id ${task.id} already exists`); diff --git a/src/tasks/task-state-logger.ts b/src/tasks/task-state-logger.ts index 9be2e1a..ca02037 100644 --- a/src/tasks/task-state-logger.ts +++ b/src/tasks/task-state-logger.ts @@ -1,5 +1,5 @@ import { TaskConfig, TaskHistoryEntry, TaskStatus } from "src/tasks/task-manager.js"; -import { BaseAuditLog } from "../base/audit-log.js"; +import { BaseAuditLog, LogUpdate } from "../base/audit-log.js"; export const DEFAULT_NAME = "task_state"; export const DEFAULT_PATH = ["logs"] as readonly string[]; @@ -10,16 +10,19 @@ export enum TaskUpdateTypeEnum { HISTORY = "history", } +export interface TaskUpdate extends LogUpdate { + taskId: string; +} + export type TaskStateData = TaskConfig | Partial | TaskHistoryEntry; -class TaskStateLogger extends BaseAuditLog { +class TaskStateLogger extends BaseAuditLog { constructor(logPath?: string) { super(DEFAULT_PATH, DEFAULT_NAME, logPath); } public logConfigCreate(taskId: string, config: TaskConfig) { this.logUpdate({ - timestamp: new Date().toISOString(), type: TaskUpdateTypeEnum.CONFIG, taskId, data: config, @@ -28,7 +31,6 @@ class TaskStateLogger extends BaseAuditLog { public logStatusChange(taskId: string, status: Partial) { this.logUpdate({ - timestamp: new Date().toISOString(), type: TaskUpdateTypeEnum.STATUS, taskId, data: status, @@ -37,7 +39,6 @@ class TaskStateLogger extends BaseAuditLog { public logHistoryEntry(taskId: string, entry: TaskHistoryEntry) { this.logUpdate({ - timestamp: new Date().toISOString(), type: TaskUpdateTypeEnum.HISTORY, taskId, data: entry, diff --git a/src/ui/agent-monitor.ts b/src/ui/agent-monitor.ts new file mode 100644 index 0000000..1489aeb --- /dev/null +++ b/src/ui/agent-monitor.ts @@ -0,0 +1,579 @@ +import blessed from "blessed"; +import chokidar from "chokidar"; +import { createReadStream } from "fs"; +import { join } from "path"; +import { createInterface } from "readline"; +import { + Agent, + AgentConfig, + AgentKind, + AvailableTool, + PoolStats, +} from "src/agents/agent-registry.js"; +import { + AgentStateUpdate, + AgentUpdateTypeEnum, + AvailableToolsData, + PoolChangeData, +} from "src/agents/agent-state-logger.js"; +import { AgentId, stringToAgentId } from "src/agents/utils.js"; +import { + applyAgentIdStyle, + applyAgentKindTypeStyle, + applyBooleanStyle, + applyNumberStyle, + applyStyle, + applyToolsStyle, + BUSY_IDLE, + DEFAULT_VERSION, + UIConfig, +} from "./ui-config.js"; +import { updateDeepPartialObject } from "src/utils/objects.js"; +import { LogInit } from "src/base/audit-log.js"; + +const AGENT_LIST_DEFAULT_TEXT = "Select pool to view agents"; +const AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT = "Select agent pool to view agent template detail"; +const AGENT_DETAIL_DEFAULT_TEXT = "Select agent to view agent detail"; +const AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT = "Select agent to view lifecycle events"; + +class AgentMonitor { + private screen: blessed.Widgets.Screen; + private poolList: blessed.Widgets.ListElement; + private poolListNameAgentIdMap = new Map(); + private agentList: blessed.Widgets.ListElement; + private agentTemplateDetail: blessed.Widgets.BoxElement; + private agentDetail: blessed.Widgets.BoxElement; + private lifecycleHistory: blessed.Widgets.BoxElement; + private logBox: blessed.Widgets.Log; + + private agents = new Map(); + private agentConfigs = new Map>(); + private agentPoolsStats = new Map>(); + private availableTools = new Map(); + private allAvailableTools = new Map(); + private selectedPoolIndex: number | null = null; + private selectedAgentIndex: number | null = null; + private lifecycleEvents = new Map< + string, + { timestamp: string; event: string; success: boolean; error?: string }[] + >(); + + constructor() { + this.screen = blessed.screen({ + smartCSR: true, + title: "Agent Registry Monitor", + debug: true, + }); + + // Left column - Pools and Agents (30%) + this.poolList = blessed.list({ + parent: this.screen, + width: "30%", + height: "20%", + left: 0, + top: 0, + border: { type: "line" }, + label: " Agent Pools ", + style: UIConfig.list, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: UIConfig.scrollbar, + }); + + this.agentList = blessed.list({ + parent: this.screen, + width: "30%", + height: "50%", + left: 0, + top: "20%", + border: { type: "line" }, + label: " Agents ", + content: AGENT_LIST_DEFAULT_TEXT, + style: UIConfig.list, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: UIConfig.scrollbar, + }); + + // Center column - Details and Tools (40%) + this.agentTemplateDetail = blessed.box({ + parent: this.screen, + width: "40%", + height: "40%", + left: "30%", + top: 0, + border: { type: "line" }, + label: " Agent Template ", + content: AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: UIConfig.scrollbar, + }); + + this.agentDetail = blessed.box({ + parent: this.screen, + width: "40%", + height: "30%", + left: "30%", + top: "40%", + border: { type: "line" }, + label: " Agent Detail ", + content: AGENT_DETAIL_DEFAULT_TEXT, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: UIConfig.scrollbar, + }); + + // Right column - Lifecycle History (30%) + this.lifecycleHistory = blessed.box({ + parent: this.screen, + width: "30%", + height: "70%", + right: 0, + top: 0, + border: { type: "line" }, + label: " Lifecycle Events ", + content: AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: UIConfig.scrollbar, + }); + + // Bottom - Live Updates + this.logBox = blessed.log({ + parent: this.screen, + width: "100%", + height: "30%", + left: 0, + bottom: 0, + border: { type: "line" }, + label: " Live Updates ", + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: UIConfig.scrollbar, + }); + + this.setupEventHandlers(); + this.screen.render(); + } + + private setupEventHandlers() { + this.screen.key(["escape", "q", "C-c"], () => process.exit(0)); + + this.poolList.on("select", (_, selectedIndex) => { + const item = this.poolList.getItem(selectedIndex); + this.selectedPoolIndex = selectedIndex; + const agentId = this.poolListNameAgentIdMap.get(item.content); + if (!agentId) { + throw new Error(`Missing agentId for pool ${item.content}`); + } + this.updateSelectedPool(agentId); + }); + + this.agentList.on("select", (_, selectedIndex) => { + const item = this.agentList.getItem(selectedIndex); + this.selectedAgentIndex = selectedIndex; + if (item?.content) { + const agentId = + item.content + .toString() + .replace(/\{[^}]+\}/g, "") + .split(" ") + .at(-1) ?? ""; + this.updateSelectedAgent(agentId); + } + }); + + // Mouse scrolling for all components + [ + this.poolList, + this.agentList, + this.agentTemplateDetail, + this.agentDetail, + this.lifecycleHistory, + ].forEach((component) => { + component.on("mouse", (data) => { + if (data.action === "wheelup") { + component.scroll(-1); + this.screen.render(); + } else if (data.action === "wheeldown") { + component.scroll(1); + this.screen.render(); + } + }); + }); + } + + private reset(shouldRender = true): void { + // Reset data + [this.agents, this.agentConfigs, this.agentPoolsStats, this.availableTools].forEach((m) => + m.clear(), + ); + + this.selectedPoolIndex = null; + this.selectedAgentIndex = null; + + // Update content + this.updatePoolList(false); + this.updateSelectedPool(undefined, false); + + // Reset log box + this.logBox.setContent(""); + this.logBox.log("Reading initial state from log..."); + + // Render + if (shouldRender) { + this.screen.render(); + } + } + + private updatePoolList(shouldRender = true): void { + this.poolListNameAgentIdMap.clear(); + const items = Array.from(this.agentPoolsStats.entries()) + .map(([kind, map]) => + Array.from(map.entries()).map(([type, stats]) => { + const content = `${applyAgentKindTypeStyle(kind as AgentKind, type)} [${applyNumberStyle(stats.available)}/${applyNumberStyle(stats.poolSize)}]`; + const agentId = { agentKind: kind, agentType: type } satisfies AgentId; + this.poolListNameAgentIdMap.set(content, agentId); + return content; + }), + ) + .flat(); + this.poolList.setItems(items); + this.updateAgentList(false); + if (shouldRender) { + this.screen.render(); + } + } + + private updateAgentList(shouldRender = true): void { + const items = Array.from(this.agents.values()).map( + (agent) => + `${applyAgentIdStyle(stringToAgentId(agent.agentId))} ${applyBooleanStyle(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`, + ); + this.agentList.setItems(items); + if (shouldRender) { + this.screen.render(); + } + } + + private updateSelectedPool(agentId?: AgentId, shouldRender = true) { + this.updateAgentConfig(agentId, false); + if (shouldRender) { + this.screen.render(); + } + } + + private updateSelectedAgent(agentId?: string, shouldRender = true) { + this.updateAgentDetails(agentId, false); + this.updateToolsList(agentId, false); + this.updateLifecycleHistory(agentId, false); + if (shouldRender) { + this.screen.render(); + } + } + + private updateAgentConfig(agentId?: AgentId, shouldRender = true): void { + if (!agentId) { + this.agentTemplateDetail.setContent(AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + + const config = this.agentConfigs.get(agentId.agentKind)?.get(agentId.agentType) as AgentConfig; + if (!config) { + this.agentTemplateDetail.setContent(AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + const details = [ + `{bold}Agent Kind:{/bold} ${applyStyle(config.kind, UIConfig.labels.agentKind)}`, + `{bold}Agent Type:{/bold} ${applyStyle(config.type, UIConfig.labels.agentType)}`, + `{bold}Max Pool Size:{/bold} ${applyNumberStyle(config.maxPoolSize)}`, + `{bold}Auto-populate pool:{/bold} ${applyBooleanStyle(config.autoPopulatePool)}`, + "", + "{bold}Description:{/bold}", + applyStyle(config.description, UIConfig.labels.description), + "", + "{bold}Instructions:{/bold}", + applyStyle(config.instructions, UIConfig.labels.input), + "", + "{bold}Tools:{/bold}", + applyToolsStyle(this.mapTools(config.tools || [])), + // `ID: ${applyAgentIdStyle(stringToAgentId(agent.agentId))}`, + // `Kind: ${applyStyle(agent.kind, UIConfig.labels.agentKind)}`, + // `Type: ${applyStyle(agent.type, UIConfig.labels.agentType)}`, + // "", + // `{bold}Status{/bold}`, + // `State: ${applyBooleanStyle(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`, + // `Instance: ${agent.instance ? "Active" : "Inactive"}`, + // "", + // config + // ? [ + // `{bold}Configuration{/bold}`, + // `Pool Size: ${applyNumberStyle(config.maxPoolSize)}`, + // `Auto Populate: ${applyBooleanStyle(config.autoPopulatePool)}`, + // "", + // `{bold}Description{/bold}`, + // applyStyle(config.description, UIConfig.labels.description), + // ].join("\n") + // : "", + ].join("\n"); + this.agentTemplateDetail.setContent(details); + if (shouldRender) { + this.screen.render(); + } + } + + private mapTools(tools: string[]): AvailableTool[] { + return tools.map( + (t) => this.allAvailableTools.get(t) ?? { name: "Undefined", description: "Lorem ipsum...." }, + ); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private updateAgentDetails(agentId?: string, shouldRender = true): void { + // const agent = this.agents.get(agentId); + // const config = this.agentConfigs.get(agentId); + // if (!agent) { + // return; + // } + // const details = [ + // `{bold}Identity{/bold}`, + // `ID: ${applyAgentIdStyle(stringToAgentId(agent.agentId))}`, + // `Kind: ${applyStyle(agent.kind, UIConfig.labels.agentKind)}`, + // `Type: ${applyStyle(agent.type, UIConfig.labels.agentType)}`, + // "", + // `{bold}Status{/bold}`, + // `State: ${applyBooleanStyle(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`, + // `Instance: ${agent.instance ? "Active" : "Inactive"}`, + // "", + // config + // ? [ + // `{bold}Configuration{/bold}`, + // `Pool Size: ${applyNumberStyle(config.maxPoolSize)}`, + // `Auto Populate: ${applyBooleanStyle(config.autoPopulatePool)}`, + // "", + // `{bold}Description{/bold}`, + // applyStyle(config.description, UIConfig.labels.description), + // ].join("\n") + // : "", + // ].join("\n"); + // this.agentTemplateDetail.setContent(details); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private updateToolsList(agentId?: string, shouldRender = true): void { + // const config = this.agentConfigs.get(agentId); + // if (!config?.tools) { + // this.agentDetail.setContent("No tools configured"); + // return; + // } + // const content = [ + // "{bold}Available Tools{/bold}", + // ...config.tools.map((tool) => `• ${applyStyle(tool, UIConfig.labels.tool)}`), + // ].join("\n"); + // this.agentDetail.setContent(content); + } + + private updateLifecycleHistory(agentId?: string, shouldRender = true): void { + if (!agentId) { + this.lifecycleHistory.setContent(AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + + const events = this.lifecycleEvents.get(agentId) || []; + const content = events.length + ? events + .map( + ({ timestamp, event, success, error }) => + `${applyStyle(timestamp, UIConfig.labels.timestamp)} ` + + `${applyStyle(event, UIConfig.labels.eventType)} ` + + `${applyBooleanStyle(success)}` + + (error ? `\n ${applyStyle(error, UIConfig.labels.error)}` : ""), + ) + .join("\n") + : "No lifecycle events recorded"; + + this.lifecycleHistory.setContent(content); + if (shouldRender) { + this.screen.render(); + } + } + + private processLogLine(line: string, shouldRender = true): void { + try { + const update: AgentStateUpdate | LogInit = JSON.parse(line); + if (update.type === "@log_init") { + // RESET + this.reset(shouldRender); + return; + } + + let data; + let poolStats; + switch (update.type) { + case AgentUpdateTypeEnum.AGENT_CONFIG: + data = update.data as AgentConfig; + this.agentConfigs.set(data.kind, new Map([[data.type, data]])); + this.agentPoolsStats.set( + data.kind, + new Map([ + [data.type, { available: 0, created: 0, inUse: 0, poolSize: data.maxPoolSize }], + ]), + ); + this.updatePoolList(false); + break; + case AgentUpdateTypeEnum.POOL: + data = update.data as PoolChangeData; + poolStats = this.agentPoolsStats.get(data.agentKind)?.get(data.agentType); + if (!poolStats) { + throw new Error( + `Missing poolStats for agentKind: ${data.agentKind}, agentType: ${data.agentType}`, + ); + } + updateDeepPartialObject(poolStats, data); + this.updatePoolList(false); + break; + case AgentUpdateTypeEnum.AVAILABLE_TOOLS: + data = update.data as AvailableToolsData; + this.availableTools.set(data.agentKind, data.availableTools ?? []); + this.allAvailableTools.clear(); + Array.from(this.availableTools.values()) + .flat() + .forEach((t) => { + this.allAvailableTools.set(t.name, t); + }); + this.updatePoolList(false); + break; + case AgentUpdateTypeEnum.AGENT: + // case AgentUpdateTypeEnum.AGENT_CONFIG: + // this.agentConfigs.set((update.data as Agent), update.data as AgentConfig); + // break; + // case AgentUpdateTypeEnum.STATUS: + // agent = this.agents.get(update.agentId!) || { + // agentId: update.agentId!, + // type: "", + // kind: "operator", + // inUse: false, + // }; + // Object.assign(agent, update.data); + // this.agents.set(update.agentId!, agent as Agent); + // break; + // case AgentUpdateTypeEnum.LIFECYCLE: + // break; + } + + // if (this.selectedAgentIndex !== null) { + // const selectedItem = this.agentList.getItem(this.selectedAgentIndex); + // if (selectedItem?.content) { + // const selectedAgentId = selectedItem.content.toString().replace(/\{[^}]+\}/g, ""); + // if (selectedAgentId === update.agentId) { + // this.updateAgentDetails(update.agentId); + // } + // } + // } + + this.logBox.log( + `${new Date().toLocaleString()} - Event ${update.type}: update ${JSON.stringify(update.data)}`, + ); + + if (shouldRender) { + this.screen.render(); + } + } catch (error) { + if (error instanceof Error) { + this.logBox.log(`Error processing log line: ${error.message}`); + } + } + } + + private async initializeStateFromLog(logPath: string): Promise { + try { + this.logBox.log("Reading initial state from log..."); + + const rl = createInterface({ + input: createReadStream(logPath, { encoding: "utf8" }), + crlfDelay: Infinity, + }); + + for await (const line of rl) { + this.processLogLine(line); + } + + this.logBox.log(`Initial state loaded: ${this.agents.size} agents found`); + this.updateAgentList(); + } catch (error) { + if (error instanceof Error) { + this.logBox.log(`Error reading initial state: ${error.message}`); + } + } + } + + private watchLogFile(logPath: string): void { + let lastProcessedSize = 0; + + chokidar + .watch(logPath, { + persistent: true, + usePolling: true, + interval: 100, + }) + .on("change", async (path) => { + try { + const rl = createInterface({ + input: createReadStream(path, { encoding: "utf8", start: lastProcessedSize }), + crlfDelay: Infinity, + }); + + for await (const line of rl) { + this.processLogLine(line); + lastProcessedSize += Buffer.from(line).length + 1; + } + } catch (error) { + if (error instanceof Error) { + this.logBox.log(`Error processing log update: ${error.message}`); + } + } + }); + } + + public async start(): Promise { + const logPath = join(process.cwd(), "logs", "agent_state.log"); + + // First read the entire log to build initial state + await this.initializeStateFromLog(logPath); + + // Then start watching for changes + this.watchLogFile(logPath); + } +} + +// Start the monitor +const monitor = new AgentMonitor(); +monitor.start(); diff --git a/src/ui/task-monitor/monitor.ts b/src/ui/task-monitor.ts similarity index 80% rename from src/ui/task-monitor/monitor.ts rename to src/ui/task-monitor.ts index ce4cb6a..2755731 100644 --- a/src/ui/task-monitor/monitor.ts +++ b/src/ui/task-monitor.ts @@ -3,13 +3,14 @@ import chokidar from "chokidar"; import { createReadStream } from "fs"; import { join } from "path"; import { createInterface } from "readline"; -import { TaskStateData, TaskUpdateTypeEnum } from "src/tasks/task-state-logger.js"; +import { stringToAgentId } from "src/agents/utils.js"; import { TaskConfig, TaskHistoryEntry, TaskStatus, TaskStatusEnum, } from "src/tasks/task-manager.js"; +import { TaskUpdate, TaskUpdateTypeEnum } from "src/tasks/task-state-logger.js"; import { truncateText } from "src/utils/text.js"; import { formatDuration } from "src/utils/time.js"; import { @@ -21,14 +22,11 @@ import { applyStyle, UIConfig, } from "./ui-config.js"; -import { stringToAgentId } from "src/agents/utils.js"; +import { LogInit } from "src/base/audit-log.js"; -interface TaskUpdate { - timestamp: string; - type: TaskUpdateTypeEnum; - taskId: string; - data: TaskStateData; -} +const TASK_CONFIG_DEFAULT_TEXT = "Select a task to view config"; +const TASK_DETAILS_DEFAULT_TEXT = "Select a task to view details"; +const TASK_HISTORY_DEFAULT_TEXT = "Select a task to view history"; class TaskMonitor { private screen: blessed.Widgets.Screen; @@ -59,24 +57,10 @@ class TaskMonitor { keys: true, vi: true, mouse: true, - style: { - selected: { bg: "blue", fg: "white" }, - border: { fg: "white" }, - item: { - hover: { bg: "blue" }, - }, - }, + style: UIConfig.list, tags: true, scrollable: true, - scrollbar: { - ch: " ", - track: { - bg: "gray", - }, - style: { - inverse: true, - }, - }, + scrollbar: UIConfig.scrollbar, }); this.taskConfig = blessed.box({ @@ -87,74 +71,50 @@ class TaskMonitor { top: 0, border: { type: "line" }, label: " Config ", - content: "Select a task to view config", + content: TASK_CONFIG_DEFAULT_TEXT, tags: true, scrollable: true, mouse: true, keys: true, vi: true, alwaysScroll: true, - scrollbar: { - ch: " ", - track: { - bg: "gray", - }, - style: { - inverse: true, - }, - }, + scrollbar: UIConfig.scrollbar, }); this.taskDetails = blessed.box({ parent: this.screen, width: "60%", - height: "40%", + height: "50%", right: 0, top: 0, border: { type: "line" }, label: " Details ", - content: "Select a task to view details", + content: TASK_DETAILS_DEFAULT_TEXT, tags: true, scrollable: true, mouse: true, keys: true, vi: true, alwaysScroll: true, - scrollbar: { - ch: " ", - track: { - bg: "gray", - }, - style: { - inverse: true, - }, - }, + scrollbar: UIConfig.scrollbar, }); this.taskHistory = blessed.box({ parent: this.screen, width: "60%", - height: "30%", + height: "20%", right: 0, - top: "40%", + top: "50%", border: { type: "line" }, label: " History ", - content: "Select a task to view history", + content: TASK_HISTORY_DEFAULT_TEXT, tags: true, scrollable: true, mouse: true, keys: true, vi: true, alwaysScroll: true, - scrollbar: { - ch: " ", - track: { - bg: "gray", - }, - style: { - inverse: true, - }, - }, + scrollbar: UIConfig.scrollbar, }); this.logBox = blessed.log({ @@ -170,17 +130,14 @@ class TaskMonitor { mouse: true, keys: true, vi: true, - scrollbar: { - ch: " ", - track: { - bg: "gray", - }, - style: { - inverse: true, - }, - }, + scrollbar: UIConfig.scrollbar, }); + this.setupEventHandlers(); + this.screen.render(); + } + + private setupEventHandlers() { this.screen.key(["escape", "q", "C-c"], () => process.exit(0)); const self = this; @@ -197,8 +154,9 @@ class TaskMonitor { .split(" ") .at(-1) ?? ""; // this.logBox.log(`Selected task: ${taskId}`); - this.updateTaskDetails(taskId); - this.updateTaskConfig(taskId); // Add this line + this.updateTaskDetails(taskId, false); + this.updateTaskConfig(taskId, false); + this.screen.render(); } }); @@ -222,8 +180,26 @@ class TaskMonitor { this.screen.render(); } }); + } - this.screen.render(); + private reset(shouldRender = true) { + // Reset data + [this.tasks, this.taskConfigs].forEach((m) => m.clear()); + this.selectedTaskIndex = null; + + // Update content + this.updateTaskList(false); + this.updateTaskDetails(undefined, false); + this.updateTaskConfig(undefined, false); + + // Reset log + this.logBox.setContent(""); + this.logBox.log("Reading initial state from log..."); + + // Render + if (shouldRender) { + this.screen.render(); + } } private updateTaskList(shouldRender = true): void { @@ -237,10 +213,21 @@ class TaskMonitor { } } - private updateTaskDetails(taskId: string, shouldRender = true): void { + private updateTaskDetails(taskId?: string, shouldRender = true): void { + if (!taskId) { + this.taskDetails.setContent(TASK_DETAILS_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + const task = this.tasks.get(taskId); if (!task) { - // this.logBox.log(`Task not found: ${taskId}`); + this.taskDetails.setContent(TASK_DETAILS_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } return; } @@ -282,7 +269,9 @@ class TaskMonitor { (entry) => `${applyStyle(new Date(entry.timestamp).toLocaleString(), UIConfig.labels.timestamp)}` + ` ${applyStatusStyle(entry.terminalStatus)}` + - ` ${applyAgentIdStyle(stringToAgentId(String(entry.agentId)))}` + + (entry.agentId && entry.agentId !== "null" + ? ` ${applyAgentIdStyle(stringToAgentId(String(entry.agentId)))}` + : "") + ` ${applyStyle(formatDuration(entry.executionTimeMs), UIConfig.labels.executionTime)}` + (entry.output ? ` ${applyStyle(truncateText(String(entry.output), 512), UIConfig.labels.output, AMBIENT_VERSION)}` @@ -302,10 +291,21 @@ class TaskMonitor { } } - private updateTaskConfig(taskId: string, shouldRender = true): void { + private updateTaskConfig(taskId?: string, shouldRender = true): void { + if (!taskId) { + this.taskConfig.setContent(TASK_CONFIG_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + const config = this.taskConfigs.get(taskId); if (!config) { - this.taskConfig.setContent("No configuration available"); + this.taskConfig.setContent(TASK_CONFIG_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } return; } @@ -345,7 +345,13 @@ class TaskMonitor { private processLogLine(line: string, shouldRender = true): void { try { - const update: TaskUpdate = JSON.parse(line); + const update: TaskUpdate | LogInit = JSON.parse(line); + if (update.type === "@log_init") { + // RESET + this.reset(shouldRender); + return; + } + const task = this.tasks.get(update.taskId) || { id: update.taskId, status: "SCHEDULED" as TaskStatusEnum, @@ -357,6 +363,8 @@ class TaskMonitor { history: [], }; + // if(update.type === '') + if (update.type === TaskUpdateTypeEnum.CONFIG) { this.taskConfigs.set(update.taskId, update.data as TaskConfig); } else if (update.type === TaskUpdateTypeEnum.STATUS) { @@ -366,7 +374,7 @@ class TaskMonitor { } this.tasks.set(update.taskId, task); - this.updateTaskList(shouldRender); + this.updateTaskList(false); // Update details if this task is currently selected const selectedIndex = this.selectedTaskIndex; @@ -375,7 +383,7 @@ class TaskMonitor { if (selectedItem?.content) { const selectedTaskId = selectedItem.content.toString().replace(/\{[^}]+\}/g, ""); if (selectedTaskId === update.taskId) { - this.updateTaskDetails(update.taskId, shouldRender); + this.updateTaskDetails(update.taskId, false); } } } @@ -405,12 +413,11 @@ class TaskMonitor { }); for await (const line of rl) { - this.processLogLine(line, false); // Don't render updates while initializing + this.processLogLine(line); // Don't render updates while initializing } this.logBox.log(`Initial state loaded: ${this.tasks.size} tasks found`); this.updateTaskList(); - this.screen.render(); } catch (error) { if (error instanceof Error) { this.logBox.log(`Error reading initial state: ${error.message}`); @@ -437,9 +444,10 @@ class TaskMonitor { }); for await (const line of rl) { - this.processLogLine(line, true); // Render updates for new changes + this.processLogLine(line); // Render updates for new changes lastProcessedSize += Buffer.from(line).length + 1; // +1 for newline } + this.screen.render(); } catch (error) { if (error instanceof Error) { this.logBox.log(`Error processing log update: ${error.message}`); diff --git a/src/ui/task-monitor/ui-config.ts b/src/ui/ui-config.ts similarity index 63% rename from src/ui/task-monitor/ui-config.ts rename to src/ui/ui-config.ts index e12973c..18e8179 100644 --- a/src/ui/task-monitor/ui-config.ts +++ b/src/ui/ui-config.ts @@ -1,4 +1,4 @@ -import { AgentKind, AgentKindSchema } from "src/agents/agent-registry.js"; +import { AgentKind, AgentKindSchema, AvailableTool } from "src/agents/agent-registry.js"; import { AgentId } from "src/agents/utils.js"; import { TaskStatusEnum } from "src/tasks/task-manager.js"; @@ -13,6 +13,7 @@ export type StyleItemValue = StyleItem | StyleItemVersioned; export type StyleCategory = Record; export const DEFAULT_VERSION = "default"; +export const BUSY_IDLE = "busy_idle"; export const AMBIENT_VERSION = "ambient"; export const UIConfig = { @@ -35,6 +36,12 @@ export const UIConfig = { error: { fg: "red", bold: true }, executionTime: { fg: "yellow" }, timestamp: { fg: "gray" }, + tool: { fg: "cyan", icon: "⚒" }, + eventType: { + fg: "yellow", + bold: true, + icon: "⚡", + }, } satisfies StyleCategory, status: { @@ -44,12 +51,41 @@ export const UIConfig = { SCHEDULED: { fg: "yellow", icon: "◆" }, // Diamond WAITING: { fg: "cyan", icon: "◇" }, // Hollow diamond STOPPED: { fg: "grey", icon: "◼" }, // Filled square - REMOVED: { fg: "darkgray", icon: "×" }, // Cross + REMOVED: { fg: "#71797E", icon: "×" }, // Cross } satisfies StyleCategory, + BUSY: { + fg: "red", + bg: null, + bold: true, + prefix: "⚡", + suffix: "", + }, + IDLE: { + fg: "green", + bg: null, + bold: false, + prefix: "○", + suffix: "", + }, + boolean: { - TRUE: { fg: "green", icon: "[✓]" }, - FALSE: { fg: "red", icon: "[✕]" }, + TRUE: { + [DEFAULT_VERSION]: { fg: "green", icon: "[✓]" }, + [BUSY_IDLE]: { + fg: "red", + bold: true, + icon: "⚡", + }, + }, + FALSE: { + [DEFAULT_VERSION]: { fg: "red", icon: "[✕]" }, + [BUSY_IDLE]: { + fg: "green", + bold: false, + icon: "○", + }, + }, } satisfies StyleCategory, borders: { @@ -57,7 +93,16 @@ export const UIConfig = { fg: "white", }, + list: { + selected: { bg: "blue", fg: "white" }, + border: { fg: "white" }, + item: { + hover: { bg: "blue" }, + }, + }, + scrollbar: { + ch: " ", track: { bg: "gray" }, style: { inverse: true }, }, @@ -74,7 +119,6 @@ export const applyStyle = ( version = "default", ) => { let style; - // = version ? Object.keys(styleItem).includes(version) ? styleItem[version] : if (version && Object.keys(styleItem).includes(version)) { style = (styleItem as StyleItemVersioned)[version]; } else { @@ -109,12 +153,36 @@ export function applyNumberStyle(count: number, inverse = false) { return applyStyle(String(count), style); } -export function applyBooleanStyle(value: boolean) { - const style = value ? UIConfig.boolean.TRUE : UIConfig.boolean.FALSE; - return applyStyle(style.icon, { fg: style.fg }); +export function applyBooleanStyle( + value: boolean, + version?: typeof DEFAULT_VERSION | typeof BUSY_IDLE, +) { + const styleVersions = value ? UIConfig.boolean.TRUE : UIConfig.boolean.FALSE; + const style = styleVersions[version ?? DEFAULT_VERSION]; + return applyStyle(style.icon, { ...style }, version); } export function applyAgentIdStyle(agentId: AgentId) { const style = UIConfig.labels.agentId[agentId.agentKind as AgentKind]; return applyStyle(`${style.icon} ${agentId.agentType}[${agentId.num}]`, { ...style }); } + +export function applyAgentKindTypeStyle(agentKind: AgentKind, agentType: string) { + const style = UIConfig.labels.agentId[agentKind as AgentKind]; + return applyStyle(`${style.icon} ${agentType}`, { ...style }); +} + +export function applyToolsStyle(tools: AvailableTool[]) { + return tools + .map((t) => [ + applyToolNameStyle(t.name), + applyStyle(t.description, UIConfig.labels.description), + "", + ]) + .join("\n"); +} + +export function applyToolNameStyle(toolName: string) { + const style = UIConfig.labels.tool; + return applyStyle(`${style.icon} ${toolName}`, style); +} From 8158988e5fceffc7fab447daaafd5d8d8c19340f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Sun, 9 Feb 2025 05:09:10 +0100 Subject: [PATCH 03/13] feat: add agent list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- package-lock.json | 1259 +++++++++++++++++++++++++++++- package.json | 8 +- src/agent.ts | 8 +- src/agents/agent-id.test.ts | 135 ++++ src/agents/agent-id.ts | 64 ++ src/agents/agent-registry.ts | 75 +- src/agents/agent-state-logger.ts | 6 +- src/agents/tool.ts | 4 +- src/agents/utils.ts | 24 - src/ui/agent-monitor.ts | 506 +++++++----- src/ui/task-monitor.ts | 2 +- src/ui/ui-config.ts | 90 ++- vite.config.ts | 18 + vitest.workspace.ts | 3 + 14 files changed, 1905 insertions(+), 297 deletions(-) create mode 100644 src/agents/agent-id.test.ts create mode 100644 src/agents/agent-id.ts delete mode 100644 src/agents/utils.ts create mode 100644 vite.config.ts create mode 100644 vitest.workspace.ts diff --git a/package-lock.json b/package-lock.json index 0a765a6..38e1f53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,9 @@ "openai": "^4.77.0", "openai-chat-tokens": "^0.2.8", "pino": "^9.4.0", + "remeda": "^2.20.1", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.5", "zod": "^3.23.8" }, "devDependencies": { @@ -352,6 +355,21 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", @@ -805,6 +823,11 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", @@ -1476,6 +1499,234 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", + "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", + "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", + "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", + "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", + "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", + "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", + "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", + "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", + "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", + "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", + "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", + "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", + "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", + "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", + "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", + "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", + "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", + "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", + "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -1528,8 +1779,7 @@ "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -1803,6 +2053,105 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@vitest/expect": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz", + "integrity": "sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==", + "dependencies": { + "@vitest/spy": "3.0.5", + "@vitest/utils": "3.0.5", + "chai": "^5.1.2", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.5.tgz", + "integrity": "sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==", + "dependencies": { + "@vitest/spy": "3.0.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.5.tgz", + "integrity": "sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.5.tgz", + "integrity": "sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==", + "dependencies": { + "@vitest/utils": "3.0.5", + "pathe": "^2.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.5.tgz", + "integrity": "sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==", + "dependencies": { + "@vitest/pretty-format": "3.0.5", + "magic-string": "^0.30.17", + "pathe": "^2.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.5.tgz", + "integrity": "sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.5.tgz", + "integrity": "sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==", + "dependencies": { + "@vitest/pretty-format": "3.0.5", + "loupe": "^3.1.2", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1946,6 +2295,14 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "engines": { + "node": ">=12" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2225,6 +2582,14 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2260,6 +2625,21 @@ } ] }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2276,6 +2656,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2533,6 +2921,14 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2630,11 +3026,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==" + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -2866,6 +3267,14 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2920,6 +3329,14 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3170,7 +3587,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -3283,7 +3699,7 @@ "version": "4.8.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", - "dev": true, + "devOptional": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -3359,6 +3775,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" + }, "node_modules/google-auth-library": { "version": "9.15.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", @@ -4086,12 +4507,25 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, - "node_modules/lru-cache": { + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==" + }, + "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/mathjs": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.0.0.tgz", @@ -4232,6 +4666,23 @@ "mustache": "bin/mustache" } }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4593,6 +5044,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4681,6 +5145,33 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4883,11 +5374,11 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/remeda": { - "version": "2.17.4", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.17.4.tgz", - "integrity": "sha512-pviU2Ag7Qx9mOCAKO4voxDx/scfLzdhp3v85qDO4xxntQsU76uE9sgrAAdK1ATn4zzaOJqCXYMMNRP+O9F4Wiw==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.20.1.tgz", + "integrity": "sha512-gsEsSmjE0CHkNp6xEsWsU/6JVNWq7rqw+ZfzNMbVV4YFIPtTj/i0FfxurTRI6Z9sAnQufU9de2Cb3xHsUTFTMA==", "dependencies": { - "type-fest": "^4.27.0" + "type-fest": "^4.33.0" } }, "node_modules/require-directory": { @@ -4948,7 +5439,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, + "devOptional": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -5024,6 +5515,43 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", + "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.6", + "@rollup/rollup-android-arm64": "4.34.6", + "@rollup/rollup-darwin-arm64": "4.34.6", + "@rollup/rollup-darwin-x64": "4.34.6", + "@rollup/rollup-freebsd-arm64": "4.34.6", + "@rollup/rollup-freebsd-x64": "4.34.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", + "@rollup/rollup-linux-arm-musleabihf": "4.34.6", + "@rollup/rollup-linux-arm64-gnu": "4.34.6", + "@rollup/rollup-linux-arm64-musl": "4.34.6", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", + "@rollup/rollup-linux-riscv64-gnu": "4.34.6", + "@rollup/rollup-linux-s390x-gnu": "4.34.6", + "@rollup/rollup-linux-x64-gnu": "4.34.6", + "@rollup/rollup-linux-x64-musl": "4.34.6", + "@rollup/rollup-win32-arm64-msvc": "4.34.6", + "@rollup/rollup-win32-ia32-msvc": "4.34.6", + "@rollup/rollup-win32-x64-msvc": "4.34.6", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5157,6 +5685,11 @@ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -5205,6 +5738,14 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -5213,6 +5754,16 @@ "node": ">= 10.x" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5465,6 +6016,16 @@ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==" + }, "node_modules/tinyglobby": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", @@ -5501,6 +6062,30 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5530,6 +6115,25 @@ "typescript": ">=4.2.0" } }, + "node_modules/tsconfck": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", + "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5539,7 +6143,7 @@ "version": "4.19.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", - "dev": true, + "devOptional": true, "dependencies": { "esbuild": "~0.23.0", "get-tsconfig": "^4.7.5" @@ -5575,9 +6179,9 @@ } }, "node_modules/type-fest": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz", - "integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==", + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.33.0.tgz", + "integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==", "engines": { "node": ">=16" }, @@ -5597,7 +6201,7 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5694,6 +6298,614 @@ "node": ">=0.10.0" } }, + "node_modules/vite": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", + "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.5.1", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.5.tgz", + "integrity": "sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.2", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/vitest": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz", + "integrity": "sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==", + "dependencies": { + "@vitest/expect": "3.0.5", + "@vitest/mocker": "3.0.5", + "@vitest/pretty-format": "^3.0.5", + "@vitest/runner": "3.0.5", + "@vitest/snapshot": "3.0.5", + "@vitest/spy": "3.0.5", + "@vitest/utils": "3.0.5", + "chai": "^5.1.2", + "debug": "^4.4.0", + "expect-type": "^1.1.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.5", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.5", + "@vitest/ui": "3.0.5", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -5736,6 +6948,21 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wikipedia": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/wikipedia/-/wikipedia-2.1.2.tgz", diff --git a/package.json b/package.json index 48bd202..9140386 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "start": "NODE_ENV=development npm run startx", "startx": "chmod +x ./start.sh && ./start.sh", "start:dev": "tsx --no-warnings src/agent.js", - "task:monitor": "tsx --inspect --no-warnings src/ui/task-monitor.js", + "task:monitor": "tsx --no-warnings src/ui/task-monitor.js", "agent:monitor": "tsx --inspect --no-warnings src/ui/agent-monitor.js", "ts:check": "tsc --noEmit --project tsconfig.json", "build": "rimraf dist && tsc", @@ -33,7 +33,8 @@ "format": "prettier --check .", "format:fix": "prettier --write .", "_ensure_env": "cp -n .env.template .env || true", - "prepare": "husky && npm run _ensure_env" + "prepare": "husky && npm run _ensure_env", + "test": "vitest --reporter=dot" }, "dependencies": { "@google-cloud/vertexai": "^1.9.2", @@ -50,6 +51,9 @@ "openai": "^4.77.0", "openai-chat-tokens": "^0.2.8", "pino": "^9.4.0", + "remeda": "^2.20.1", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.5", "zod": "^3.23.8" }, "devDependencies": { diff --git a/src/agent.ts b/src/agent.ts index 9795344..0d296fe 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -9,7 +9,7 @@ import * as supervisor from "./agents/supervisor.js"; import { createConsoleReader } from "./helpers/reader.js"; import { TaskManager } from "./tasks/task-manager.js"; import { getLogger, LoggerType } from "./helpers/tmux-logger.js"; -import { agentIdToString } from "./agents/utils.js"; +import { agentIdToString } from "./agents/agent-id.js"; import { getAgentStateLogger } from "./agents/agent-state-logger.js"; import { getTaskStateLogger } from "./tasks/task-state-logger.js"; @@ -24,7 +24,7 @@ const registry = new AgentRegistry({ poolStats, toolsFactory, ): Promise<{ agentId: string; instance: BeeAgent }> { - const { kind: agentKind, type: agentType, instructions, description } = config; + const { agentKind: agentKind, agentType: agentType, instructions, description } = config; const num = poolStats.created + 1; const agentId = agentIdToString({ agentKind, agentType, num }); const tools = config.tools == null ? toolsFactory.getAvailableToolsNames() : config.tools; @@ -97,8 +97,8 @@ registry.registerToolsFactories([ registry.registerAgentType({ autoPopulatePool: false, - kind: AgentKindSchema.Enum.supervisor, - type: supervisor.AgentTypes.BOSS, + agentKind: AgentKindSchema.Enum.supervisor, + agentType: supervisor.AgentTypes.BOSS, instructions: "", description: "The boss supervisor agent that control whole app.", maxPoolSize: 1, diff --git a/src/agents/agent-id.test.ts b/src/agents/agent-id.test.ts new file mode 100644 index 0000000..ec56ac4 --- /dev/null +++ b/src/agents/agent-id.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from "vitest"; +import { + AgentId, + AgentPoolTypeId, + agentIdToString, + agentPoolTypeIdToString, + stringToAgentId, + stringAgentIdToAgentPoolTypeId, +} from "./agent-id.js"; +import { AgentKindSchema } from "./agent-registry.js"; + +describe("Agent ID Functions", () => { + describe("agentPoolIdToString", () => { + it("should convert AgentPoolId to string format", () => { + const agentPoolId: AgentPoolTypeId = { + agentKind: AgentKindSchema.Values.operator, + agentType: "worker", + }; + + expect(agentPoolTypeIdToString(agentPoolId)).toBe("operator:worker"); + }); + + it("should handle different agent kinds and types", () => { + const cases: [AgentPoolTypeId, string][] = [ + [{ agentKind: AgentKindSchema.Values.operator, agentType: "test" }, "operator:test"], + [{ agentKind: AgentKindSchema.Values.supervisor, agentType: "api" }, "supervisor:api"], + [ + { agentKind: AgentKindSchema.Values.operator, agentType: "special-type" }, + "operator:special-type", + ], + ]; + + cases.forEach(([input, expected]) => { + expect(agentPoolTypeIdToString(input)).toBe(expected); + }); + }); + }); + + describe("stringToAgentPoolId", () => { + it("should convert string to AgentPoolId", () => { + const result = stringAgentIdToAgentPoolTypeId("operator:worker"); + + expect(result).toEqual({ + agentKind: AgentKindSchema.Values.operator, + agentType: "worker", + }); + }); + + it("should handle strings with brackets", () => { + const result = stringAgentIdToAgentPoolTypeId("operator:worker[1]"); + + expect(result).toEqual({ + agentKind: AgentKindSchema.Values.operator, + agentType: "worker", + }); + }); + + it("should handle different formats", () => { + const cases = [ + ["supervisor:api", { agentKind: AgentKindSchema.Values.supervisor, agentType: "api" }], + ["operator:test[5]", { agentKind: AgentKindSchema.Values.operator, agentType: "test" }], + ]; + + cases.forEach(([input, expected]) => { + expect(stringAgentIdToAgentPoolTypeId(input as string)).toEqual(expected); + }); + }); + }); + + describe("agentIdToString", () => { + it("should convert AgentId to string format", () => { + const agentId: AgentId = { + agentKind: AgentKindSchema.Values.operator, + agentType: "worker", + num: 1, + }; + + expect(agentIdToString(agentId)).toBe("operator:worker[1]"); + }); + + it("should handle different agent numbers", () => { + const cases: [AgentId, string][] = [ + [ + { agentKind: AgentKindSchema.Values.operator, agentType: "test", num: 0 }, + "operator:test[0]", + ], + [ + { agentKind: AgentKindSchema.Values.supervisor, agentType: "api", num: 999 }, + "supervisor:api[999]", + ], + [ + { agentKind: AgentKindSchema.Values.operator, agentType: "worker", num: -1 }, + "operator:worker[-1]", + ], + ]; + + cases.forEach(([input, expected]) => { + expect(agentIdToString(input)).toBe(expected); + }); + }); + }); + + describe("stringToAgentId", () => { + it("should convert string to AgentId", () => { + const result = stringToAgentId("operator:worker[1]"); + + expect(result).toEqual({ + agentKind: AgentKindSchema.Values.operator, + agentType: "worker", + num: 1, + }); + }); + + it("should handle various formats", () => { + const cases = [ + [ + "supervisor:api[5]", + { agentKind: AgentKindSchema.Values.supervisor, agentType: "api", num: 5 }, + ], + [ + `operator:test[-1]`, + { agentKind: AgentKindSchema.Values.operator, agentType: "test", num: -1 }, + ], + [ + "operator:worker[0]", + { agentKind: AgentKindSchema.Values.operator, agentType: "worker", num: 0 }, + ], + ]; + + cases.forEach(([input, expected]) => { + expect(stringToAgentId(input as string)).toEqual(expected); + }); + }); + }); +}); diff --git a/src/agents/agent-id.ts b/src/agents/agent-id.ts new file mode 100644 index 0000000..b81dfb7 --- /dev/null +++ b/src/agents/agent-id.ts @@ -0,0 +1,64 @@ +import { AgentKind } from "./agent-registry.js"; + +export interface AgentId extends AgentPoolTypeId { + num: number; +} + +export interface AgentPoolTypeId extends AgentPoolId { + agentType: string; +} + +export interface AgentPoolId { + agentKind: AgentKind; +} + +export function agentPoolIdToString(agentPoolId: AgentPoolId) { + return `${agentPoolId.agentKind}`; +} + +export function agentPoolTypeIdToString(agentPoolId: AgentPoolTypeId) { + return `${agentPoolIdToString(agentPoolId)}:${agentPoolId.agentType}`; +} + +export function stringToAgentPoolId(agentPoolId: string): AgentPoolId { + return { + agentKind: agentPoolId as AgentKind, + }; +} + +export function stringToAgentPoolTypeId(agentPoolTypeId: string): AgentPoolTypeId { + const [kind, rest] = agentPoolTypeId.split(":"); + + return { + agentKind: kind as AgentKind, + agentType: rest, + }; +} + +export function stringAgentIdToAgentPoolTypeId(agentPoolId: string): AgentPoolTypeId { + const [kind, rest] = agentPoolId.split(":"); + const [type] = rest.split("["); + + return { + agentKind: kind as AgentKind, + agentType: type, + }; +} + +export function agentIdToString(agentId: AgentId) { + return `${agentPoolTypeIdToString(agentId)}[${agentId.num}]`; +} + +export function stringToAgentId(agentId: string): AgentId { + const agentPoolId = stringAgentIdToAgentPoolTypeId(agentId); + const num = parseInt((agentId.match(/\[(.*?)\]/) || [])[1]); + + if (num == null) { + throw new Error(`AgentId ${agentId} valid num is not presence`); + } + + return { + ...agentPoolId, + num, + }; +} diff --git a/src/agents/agent-registry.ts b/src/agents/agent-registry.ts index 5428c60..b9a8f8f 100644 --- a/src/agents/agent-registry.ts +++ b/src/agents/agent-registry.ts @@ -2,6 +2,7 @@ import { Logger } from "bee-agent-framework/logger/logger"; import { BaseToolsFactory } from "src/base/tools-factory.js"; import { z } from "zod"; import { getAgentStateLogger } from "./agent-state-logger.js"; +import { stringToAgentId } from "./agent-id.js"; export const AgentKindSchema = z .enum(["supervisor", "operator"]) @@ -15,8 +16,8 @@ export type AgentKind = z.infer; * Defines the basic properties and requirements for creating agents of a specific type. */ export const AgentConfigSchema = z.object({ - kind: AgentKindSchema, - type: z.string().describe("Unique identifier for the agent type"), + agentKind: AgentKindSchema, + agentType: z.string().describe("Unique identifier for the agent type"), instructions: z.string().describe("Provide detailed instructions on how the agent should act."), description: z .string() @@ -48,8 +49,8 @@ export const AgentSchema = z.object({ /** Unique identifier for this specific agent instance */ agentId: z.string(), /** The type of agent this instance represents */ - type: z.string(), - kind: AgentKindSchema, + agentType: z.string(), + agentKind: AgentKindSchema, /** Configuration settings for this agent */ config: AgentConfigSchema, /** @@ -242,7 +243,7 @@ export class AgentRegistry { * @throws Error if agent type is already registered */ registerAgentType(config: AgentConfig): void { - const { kind, type, maxPoolSize, autoPopulatePool } = config; + const { agentKind: kind, agentType: type, maxPoolSize, autoPopulatePool } = config; this.logger.info("Registering new agent type", { kind: kind, type: type, @@ -255,7 +256,7 @@ export class AgentRegistry { throw new Error(`Agent type '${type}' is already registered`); } - const toolsFactory = this.getToolsFactory(config.kind); + const toolsFactory = this.getToolsFactory(config.agentKind); const availableTools = toolsFactory.getAvailableTools(); if (config.tools?.filter((it) => !!it.length).length) { const undefinedTools = config.tools.filter( @@ -411,12 +412,15 @@ export class AgentRegistry { this.logger.trace("Executing onAcquire callback", { agentId }); await this.lifecycleCallbacks.onAcquire(agentId); } - getAgentStateLogger().logAgentLifeCycle({ event: "onAcquire", agentId: agent.agentId }); + getAgentStateLogger().logAgentLifeCycle({ + event: "onAcquire", + agentId: stringToAgentId(agent.agentId), + }); - const poolStats = this.getPoolStats(agent.kind, agent.type); + const poolStats = this.getPoolStats(agent.agentKind, agent.agentType); getAgentStateLogger().logPoolChange({ - agentKind: agent.kind, - agentType: agent.type, + agentKind: agent.agentKind, + agentType: agent.agentType, ...poolStats, }); return agent as AgentWithInstance; @@ -435,7 +439,11 @@ export class AgentRegistry { throw new Error(`Agent with ID '${agentId}' not found`); } - const { kind, type, maxPoolSize } = this.getAgentTypeConfig(agent.kind, agent.type); + const { + agentKind: kind, + agentType: type, + maxPoolSize, + } = this.getAgentTypeConfig(agent.agentKind, agent.agentType); const pool = this.getAgentPoolMap(kind, type); if (!pool || maxPoolSize === 0) { @@ -452,13 +460,16 @@ export class AgentRegistry { // Return to pool agent.inUse = false; pool.add(agentId); - this.logger.debug("Agent released back to pool", { agentId, type: agent.type }); - getAgentStateLogger().logAgentLifeCycle({ event: "onRelease", agentId: agent.agentId }); + this.logger.debug("Agent released back to pool", { agentId, type: agent.agentType }); + getAgentStateLogger().logAgentLifeCycle({ + event: "onRelease", + agentId: stringToAgentId(agent.agentId), + }); - const poolStats = this.getPoolStats(agent.kind, agent.type); + const poolStats = this.getPoolStats(agent.agentKind, agent.agentType); getAgentStateLogger().logPoolChange({ - agentKind: agent.kind, - agentType: agent.type, + agentKind: agent.agentKind, + agentType: agent.agentType, ...poolStats, }); } @@ -487,8 +498,8 @@ export class AgentRegistry { const agent = { agentId, - kind, - type, + agentKind: kind, + agentType: type, config, inUse: !forPool, instance, @@ -497,7 +508,10 @@ export class AgentRegistry { this.logger.info("Agent created successfully", { agentId, type, forPool }); - getAgentStateLogger().logAgentLifeCycle({ event: "onCreate", agentId: agent.agentId }); + getAgentStateLogger().logAgentLifeCycle({ + event: "onCreate", + agentId: stringToAgentId(agentId), + }); poolStats = this.getPoolStats(kind, type); getAgentStateLogger().logPoolChange({ agentKind: kind, agentType: type, ...poolStats }); @@ -518,10 +532,14 @@ export class AgentRegistry { } // Remove from pool if it's in one - const pool = this.getAgentPoolMap(agent.kind, agent.type); + const pool = this.getAgentPoolMap(agent.agentKind, agent.agentType); if (pool) { pool.delete(agentId); - this.logger.trace("Removed agent from pool", { agentId, kind: agent.kind, type: agent.type }); + this.logger.trace("Removed agent from pool", { + agentId, + kind: agent.agentKind, + type: agent.agentType, + }); } else { throw new Error(`Missing pool`); } @@ -530,16 +548,19 @@ export class AgentRegistry { this.agents.delete(agentId); this.logger.info("Agent destroyed successfully", { agentId, - kind: agent.kind, - type: agent.type, + kind: agent.agentKind, + type: agent.agentType, }); - getAgentStateLogger().logAgentLifeCycle({ event: "onDestroy", agentId }); + getAgentStateLogger().logAgentLifeCycle({ + event: "onDestroy", + agentId: stringToAgentId(agentId), + }); - const poolStats = this.getPoolStats(agent.kind, agent.type); + const poolStats = this.getPoolStats(agent.agentKind, agent.agentType); getAgentStateLogger().logPoolChange({ - agentKind: agent.kind, - agentType: agent.type, + agentKind: agent.agentKind, + agentType: agent.agentType, ...poolStats, }); } diff --git a/src/agents/agent-state-logger.ts b/src/agents/agent-state-logger.ts index ee96f1a..3bff2ca 100644 --- a/src/agents/agent-state-logger.ts +++ b/src/agents/agent-state-logger.ts @@ -1,5 +1,7 @@ +import { TaskConfig, TaskHistoryEntry } from "src/tasks/task-manager.js"; import { BaseAuditLog, LogUpdate } from "../base/audit-log.js"; import { AgentConfig, AgentKind, AvailableTool } from "./agent-registry.js"; +import { AgentId } from "./agent-id.js"; export const DEFAULT_NAME = "agent_state"; export const DEFAULT_PATH = ["logs"] as readonly string[]; @@ -27,7 +29,9 @@ export interface PoolChangeData { export interface AgentLifecycleData { event: "onCreate" | "onDestroy" | "onAcquire" | "onRelease"; - agentId: string; + agentId: AgentId; + taskConfig?: TaskConfig; + historyEntry?: TaskHistoryEntry; } export type AgentStateData = AgentConfig | AvailableToolsData | PoolChangeData | AgentLifecycleData; diff --git a/src/agents/tool.ts b/src/agents/tool.ts index 86fd594..1b95389 100644 --- a/src/agents/tool.ts +++ b/src/agents/tool.ts @@ -53,7 +53,7 @@ export const RegisterAgentTypeSchema = z method: z.literal("registerAgentType"), agentKind: z.literal(AgentKindSchema.Enum.operator), config: AgentConfigSchema.omit({ - kind: true, + agentKind: true, }), }) .describe("Register a new agent type with its configuration."); @@ -149,7 +149,7 @@ export class AgentRegistryTool extends Tool< data = this.registry.getToolsFactory(input.agentKind).getAvailableTools(); break; case "registerAgentType": - data = this.registry.registerAgentType({ ...input.config, kind: input.agentKind }); + data = this.registry.registerAgentType({ ...input.config, agentKind: input.agentKind }); break; case "getAgentTypes": data = this.registry.getAgentTypes(); diff --git a/src/agents/utils.ts b/src/agents/utils.ts deleted file mode 100644 index b2c8450..0000000 --- a/src/agents/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AgentKind } from "./agent-registry.js"; - -export const UNDEFINED_NUM = -1; - -export interface AgentId { - agentKind: AgentKind; - agentType: string; - num?: number; -} - -export function agentIdToString(agentId: AgentId) { - return `${agentId.agentKind}:${agentId.agentType}[${agentId.num ?? UNDEFINED_NUM}]`; -} - -export function stringToAgentId(agentId: string): AgentId { - const [kind, rest] = agentId.split(":"); - const match = rest.match(/([^[]+)(?:\[(\d+)\])?/); - - return { - agentKind: kind as AgentKind, - agentType: match?.[1] ?? "", - num: match?.[2] ? parseInt(match[2]) : -1, - }; -} diff --git a/src/ui/agent-monitor.ts b/src/ui/agent-monitor.ts index 1489aeb..59a3ee1 100644 --- a/src/ui/agent-monitor.ts +++ b/src/ui/agent-monitor.ts @@ -4,55 +4,78 @@ import { createReadStream } from "fs"; import { join } from "path"; import { createInterface } from "readline"; import { - Agent, + AgentId, + agentIdToString, + AgentPoolId, + agentPoolIdToString, + AgentPoolTypeId, + agentPoolTypeIdToString, + stringAgentIdToAgentPoolTypeId, + stringToAgentPoolId, + stringToAgentPoolTypeId, +} from "src/agents/agent-id.js"; +import { AgentConfig, AgentKind, + AgentKindSchema, AvailableTool, PoolStats, } from "src/agents/agent-registry.js"; import { + AgentLifecycleData, AgentStateUpdate, AgentUpdateTypeEnum, AvailableToolsData, PoolChangeData, } from "src/agents/agent-state-logger.js"; -import { AgentId, stringToAgentId } from "src/agents/utils.js"; -import { - applyAgentIdStyle, - applyAgentKindTypeStyle, - applyBooleanStyle, - applyNumberStyle, - applyStyle, - applyToolsStyle, - BUSY_IDLE, - DEFAULT_VERSION, - UIConfig, -} from "./ui-config.js"; -import { updateDeepPartialObject } from "src/utils/objects.js"; import { LogInit } from "src/base/audit-log.js"; +import { TaskConfig, TaskHistoryEntry } from "src/tasks/task-manager.js"; +import { updateDeepPartialObject } from "src/utils/objects.js"; +import * as st from "./ui-config.js"; const AGENT_LIST_DEFAULT_TEXT = "Select pool to view agents"; -const AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT = "Select agent pool to view agent template detail"; +const AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT = "Select agent pool to view agent config detail"; const AGENT_DETAIL_DEFAULT_TEXT = "Select agent to view agent detail"; const AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT = "Select agent to view lifecycle events"; +export interface Agent { + agentId: AgentId; + inUse: boolean; + isDestroyed: boolean; + assignedTaskConfig: TaskConfig | null; + history: TaskHistoryEntry[]; +} + +export interface AgentPool { + agentPoolTypeId: AgentPoolTypeId; + agentConfig: AgentConfig; + poolStats: PoolStats; +} + class AgentMonitor { private screen: blessed.Widgets.Screen; - private poolList: blessed.Widgets.ListElement; - private poolListNameAgentIdMap = new Map(); + private agentPools = new Map>(); + private agentPoolList: blessed.Widgets.ListElement; + private agentPoolListItemsData: { + agentPoolId: AgentPoolId | AgentPoolTypeId; + itemContent: string; + }[] = []; + private agentPoolListSelectedIndex: number | null = null; + + private agents = new Map(); private agentList: blessed.Widgets.ListElement; + private agentListItemsData: { + agent: Agent; + itemContent: string; + }[] = []; + private agentListSelectedIndex: number | null = null; private agentTemplateDetail: blessed.Widgets.BoxElement; private agentDetail: blessed.Widgets.BoxElement; private lifecycleHistory: blessed.Widgets.BoxElement; private logBox: blessed.Widgets.Log; - private agents = new Map(); - private agentConfigs = new Map>(); - private agentPoolsStats = new Map>(); private availableTools = new Map(); private allAvailableTools = new Map(); - private selectedPoolIndex: number | null = null; - private selectedAgentIndex: number | null = null; private lifecycleEvents = new Map< string, { timestamp: string; event: string; success: boolean; error?: string }[] @@ -66,7 +89,7 @@ class AgentMonitor { }); // Left column - Pools and Agents (30%) - this.poolList = blessed.list({ + this.agentPoolList = blessed.list({ parent: this.screen, width: "30%", height: "20%", @@ -74,13 +97,13 @@ class AgentMonitor { top: 0, border: { type: "line" }, label: " Agent Pools ", - style: UIConfig.list, + style: st.UIConfig.list, tags: true, scrollable: true, mouse: true, keys: true, vi: true, - scrollbar: UIConfig.scrollbar, + scrollbar: st.UIConfig.scrollbar, }); this.agentList = blessed.list({ @@ -92,13 +115,13 @@ class AgentMonitor { border: { type: "line" }, label: " Agents ", content: AGENT_LIST_DEFAULT_TEXT, - style: UIConfig.list, + style: st.UIConfig.list, tags: true, scrollable: true, mouse: true, keys: true, vi: true, - scrollbar: UIConfig.scrollbar, + scrollbar: st.UIConfig.scrollbar, }); // Center column - Details and Tools (40%) @@ -109,14 +132,14 @@ class AgentMonitor { left: "30%", top: 0, border: { type: "line" }, - label: " Agent Template ", + label: " Agent Config ", content: AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT, tags: true, scrollable: true, mouse: true, keys: true, vi: true, - scrollbar: UIConfig.scrollbar, + scrollbar: st.UIConfig.scrollbar, }); this.agentDetail = blessed.box({ @@ -133,7 +156,7 @@ class AgentMonitor { mouse: true, keys: true, vi: true, - scrollbar: UIConfig.scrollbar, + scrollbar: st.UIConfig.scrollbar, }); // Right column - Lifecycle History (30%) @@ -151,7 +174,7 @@ class AgentMonitor { mouse: true, keys: true, vi: true, - scrollbar: UIConfig.scrollbar, + scrollbar: st.UIConfig.scrollbar, }); // Bottom - Live Updates @@ -168,7 +191,7 @@ class AgentMonitor { mouse: true, keys: true, vi: true, - scrollbar: UIConfig.scrollbar, + scrollbar: st.UIConfig.scrollbar, }); this.setupEventHandlers(); @@ -178,33 +201,41 @@ class AgentMonitor { private setupEventHandlers() { this.screen.key(["escape", "q", "C-c"], () => process.exit(0)); - this.poolList.on("select", (_, selectedIndex) => { - const item = this.poolList.getItem(selectedIndex); - this.selectedPoolIndex = selectedIndex; - const agentId = this.poolListNameAgentIdMap.get(item.content); - if (!agentId) { - throw new Error(`Missing agentId for pool ${item.content}`); + this.agentPoolList.on("select", (_, selectedIndex) => { + this.agentPoolListSelectedIndex = selectedIndex; + const itemData = this.agentPoolListItemsData[this.agentPoolListSelectedIndex]; + if (!itemData) { + throw new Error(`Missing data for selectedIndex:${this.agentPoolListSelectedIndex}`); } - this.updateSelectedPool(agentId); + let agentConfig; + const agentPoolId = itemData.agentPoolId; + if ((agentPoolId as AgentPoolTypeId).agentType) { + agentConfig = this.agentPools + .get(agentPoolIdToString(agentPoolId)) + ?.get(agentPoolTypeIdToString(agentPoolId as AgentPoolTypeId))?.agentConfig; + } + + this.updateAgentConfig(agentConfig, false); + this.updateAgentList(); }); this.agentList.on("select", (_, selectedIndex) => { - const item = this.agentList.getItem(selectedIndex); - this.selectedAgentIndex = selectedIndex; - if (item?.content) { - const agentId = - item.content - .toString() - .replace(/\{[^}]+\}/g, "") - .split(" ") - .at(-1) ?? ""; - this.updateSelectedAgent(agentId); + this.agentListSelectedIndex = selectedIndex; + const itemData = this.agentListItemsData[this.agentListSelectedIndex]; + if (!itemData) { + throw new Error(`Missing data for selectedIndex:${this.agentPoolListSelectedIndex}`); } + const { agent } = itemData; + const agentConfig = this.agentPools + .get(agentPoolIdToString(agent.agentId)) + ?.get(agentPoolTypeIdToString(agent.agentId))?.agentConfig; + this.updateAgentConfig(agentConfig, false); + this.updateAgentDetails(itemData.agent); }); // Mouse scrolling for all components [ - this.poolList, + this.agentPoolList, this.agentList, this.agentTemplateDetail, this.agentDetail, @@ -224,16 +255,15 @@ class AgentMonitor { private reset(shouldRender = true): void { // Reset data - [this.agents, this.agentConfigs, this.agentPoolsStats, this.availableTools].forEach((m) => - m.clear(), - ); - - this.selectedPoolIndex = null; - this.selectedAgentIndex = null; + [this.agents, this.agentPools, this.agents, this.availableTools].forEach((m) => { + m.clear(); + }); // Update content - this.updatePoolList(false); - this.updateSelectedPool(undefined, false); + this.updateAgentPoolsList(false); + this.updateAgentConfig(undefined, false); + this.updateAgentList(false); + this.updateAgentConfig(undefined, false); // Reset log box this.logBox.setContent(""); @@ -245,54 +275,98 @@ class AgentMonitor { } } - private updatePoolList(shouldRender = true): void { - this.poolListNameAgentIdMap.clear(); - const items = Array.from(this.agentPoolsStats.entries()) - .map(([kind, map]) => - Array.from(map.entries()).map(([type, stats]) => { - const content = `${applyAgentKindTypeStyle(kind as AgentKind, type)} [${applyNumberStyle(stats.available)}/${applyNumberStyle(stats.poolSize)}]`; - const agentId = { agentKind: kind, agentType: type } satisfies AgentId; - this.poolListNameAgentIdMap.set(content, agentId); - return content; - }), - ) - .flat(); - this.poolList.setItems(items); - this.updateAgentList(false); - if (shouldRender) { - this.screen.render(); - } - } + private updateAgentPoolsList(shouldRender = true): void { + this.agentPoolListItemsData.splice(0); + Array.from(this.agentPools.entries()) + .sort(([a], [b]) => { + // Sort agent kind + const aPoolId = stringToAgentPoolId(a); + const bPoolId = stringToAgentPoolId(b); + const aSuper = aPoolId.agentKind === AgentKindSchema.Values.supervisor; + const bSuper = bPoolId.agentKind === AgentKindSchema.Values.supervisor; + if (aSuper && !bSuper) { + return -1; + } else if (!aSuper && bSuper) { + return 1; + } else { + return aPoolId.agentKind.localeCompare(bPoolId.agentKind); + } + }) + .forEach(([agentPoolIdStr, agentTypePools]) => { + const agentPoolTypeId = stringToAgentPoolId(agentPoolIdStr); + this.agentPoolListItemsData.push({ + agentPoolId: agentPoolTypeId, + itemContent: st.agentPoolId(agentPoolTypeId), + }); + Array.from(agentTypePools.entries()) + .sort(([a], [b]) => { + // Sort agent type + const aPoolId = stringAgentIdToAgentPoolTypeId(a); + const bPoolId = stringAgentIdToAgentPoolTypeId(b); + return aPoolId.agentType.localeCompare(bPoolId.agentType); + }) + .forEach(([agentPoolTypeIdStr, agentPool]) => { + const agentPoolTypeId = stringToAgentPoolTypeId(agentPoolTypeIdStr); + this.agentPoolListItemsData.push({ + agentPoolId: agentPoolTypeId, + itemContent: st.agentPool(agentPool), + }); + }); + }); - private updateAgentList(shouldRender = true): void { - const items = Array.from(this.agents.values()).map( - (agent) => - `${applyAgentIdStyle(stringToAgentId(agent.agentId))} ${applyBooleanStyle(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`, - ); - this.agentList.setItems(items); - if (shouldRender) { - this.screen.render(); + if (this.agentPoolListSelectedIndex == null && this.agentPoolListItemsData.length) { + this.agentPoolListSelectedIndex = 0; + this.agentPoolList.select(this.agentPoolListSelectedIndex); } - } + this.agentPoolList.setItems(this.agentPoolListItemsData.map((it) => it.itemContent)); - private updateSelectedPool(agentId?: AgentId, shouldRender = true) { - this.updateAgentConfig(agentId, false); + this.updateAgentList(false); if (shouldRender) { this.screen.render(); } } - private updateSelectedAgent(agentId?: string, shouldRender = true) { - this.updateAgentDetails(agentId, false); - this.updateToolsList(agentId, false); - this.updateLifecycleHistory(agentId, false); + private updateAgentList(shouldRender = true): void { + this.agentListItemsData.splice(0); + Array.from(this.agents.entries()) + .filter(([, a]) => { + if (this.agentPoolListSelectedIndex == null) { + return false; + } + const agentPoolListItem = this.agentPoolListItemsData[this.agentPoolListSelectedIndex]; + if (agentPoolListItem.agentPoolId.agentKind === a.agentId.agentKind) { + if ((agentPoolListItem.agentPoolId as AgentPoolTypeId).agentType != null) { + return ( + (agentPoolListItem.agentPoolId as AgentPoolTypeId).agentType === a.agentId.agentType + ); + } else { + return true; + } + } + }) + .sort(([, a], [, b]) => { + const comp = a.agentId.agentType.localeCompare(b.agentId.agentType); + if (comp === 0) { + return Math.sign(a.agentId.num - b.agentId.num); + } else { + return comp; + } + }) + .forEach(([, agent]) => { + this.agentListItemsData.push({ + agent, + itemContent: st.agent(agent), + }); + }); + this.agentList.setItems(this.agentListItemsData.map((it) => it.itemContent)); + this.agentList.setContent(this.agentListItemsData.length ? "" : AGENT_LIST_DEFAULT_TEXT); if (shouldRender) { this.screen.render(); } } - private updateAgentConfig(agentId?: AgentId, shouldRender = true): void { - if (!agentId) { + private updateAgentConfig(agentConfig?: AgentConfig, shouldRender = true): void { + if (!agentConfig) { this.agentTemplateDetail.setContent(AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT); if (shouldRender) { this.screen.render(); @@ -300,46 +374,20 @@ class AgentMonitor { return; } - const config = this.agentConfigs.get(agentId.agentKind)?.get(agentId.agentType) as AgentConfig; - if (!config) { - this.agentTemplateDetail.setContent(AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - return; - } const details = [ - `{bold}Agent Kind:{/bold} ${applyStyle(config.kind, UIConfig.labels.agentKind)}`, - `{bold}Agent Type:{/bold} ${applyStyle(config.type, UIConfig.labels.agentType)}`, - `{bold}Max Pool Size:{/bold} ${applyNumberStyle(config.maxPoolSize)}`, - `{bold}Auto-populate pool:{/bold} ${applyBooleanStyle(config.autoPopulatePool)}`, + `{bold}Agent Kind:{/bold} ${st.agentKind(agentConfig.agentKind)}`, + `{bold}Agent Type:{/bold} ${st.agentType(agentConfig.agentType)}`, + `{bold}Max Pool Size:{/bold} ${st.num(agentConfig.maxPoolSize)}`, + `{bold}Auto-populate pool:{/bold} ${st.bool(agentConfig.autoPopulatePool)}`, "", "{bold}Description:{/bold}", - applyStyle(config.description, UIConfig.labels.description), + st.desc(agentConfig.description), "", "{bold}Instructions:{/bold}", - applyStyle(config.instructions, UIConfig.labels.input), + st.desc(agentConfig.instructions), "", "{bold}Tools:{/bold}", - applyToolsStyle(this.mapTools(config.tools || [])), - // `ID: ${applyAgentIdStyle(stringToAgentId(agent.agentId))}`, - // `Kind: ${applyStyle(agent.kind, UIConfig.labels.agentKind)}`, - // `Type: ${applyStyle(agent.type, UIConfig.labels.agentType)}`, - // "", - // `{bold}Status{/bold}`, - // `State: ${applyBooleanStyle(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`, - // `Instance: ${agent.instance ? "Active" : "Inactive"}`, - // "", - // config - // ? [ - // `{bold}Configuration{/bold}`, - // `Pool Size: ${applyNumberStyle(config.maxPoolSize)}`, - // `Auto Populate: ${applyBooleanStyle(config.autoPopulatePool)}`, - // "", - // `{bold}Description{/bold}`, - // applyStyle(config.description, UIConfig.labels.description), - // ].join("\n") - // : "", + st.tools(this.mapTools(agentConfig.tools || [])), ].join("\n"); this.agentTemplateDetail.setContent(details); if (shouldRender) { @@ -353,49 +401,34 @@ class AgentMonitor { ); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - private updateAgentDetails(agentId?: string, shouldRender = true): void { - // const agent = this.agents.get(agentId); - // const config = this.agentConfigs.get(agentId); - // if (!agent) { - // return; - // } - // const details = [ - // `{bold}Identity{/bold}`, - // `ID: ${applyAgentIdStyle(stringToAgentId(agent.agentId))}`, - // `Kind: ${applyStyle(agent.kind, UIConfig.labels.agentKind)}`, - // `Type: ${applyStyle(agent.type, UIConfig.labels.agentType)}`, - // "", - // `{bold}Status{/bold}`, - // `State: ${applyBooleanStyle(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`, - // `Instance: ${agent.instance ? "Active" : "Inactive"}`, - // "", - // config - // ? [ - // `{bold}Configuration{/bold}`, - // `Pool Size: ${applyNumberStyle(config.maxPoolSize)}`, - // `Auto Populate: ${applyBooleanStyle(config.autoPopulatePool)}`, - // "", - // `{bold}Description{/bold}`, - // applyStyle(config.description, UIConfig.labels.description), - // ].join("\n") - // : "", - // ].join("\n"); - // this.agentTemplateDetail.setContent(details); - } + private updateAgentDetails(agent?: Agent, shouldRender = true): void { + if (!agent) { + this.agentDetail.setContent(AGENT_DETAIL_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - private updateToolsList(agentId?: string, shouldRender = true): void { - // const config = this.agentConfigs.get(agentId); - // if (!config?.tools) { - // this.agentDetail.setContent("No tools configured"); - // return; - // } - // const content = [ - // "{bold}Available Tools{/bold}", - // ...config.tools.map((tool) => `• ${applyStyle(tool, UIConfig.labels.tool)}`), - // ].join("\n"); - // this.agentDetail.setContent(content); + const details = [ + `${st.label("ID")}: ${st.agentId(agent.agentId)}`, + `${st.label("In Use")}: ${st.bool(agent.inUse, "busy_idle")}`, + `${st.label("Is destroyed")}: ${st.bool(agent.isDestroyed, "inverse_color")}`, + ...(agent.assignedTaskConfig + ? [ + "", + `${st.label("Task")}: ${st.taskId(agent.assignedTaskConfig.id)}`, + `${st.label("Description")}:`, + `${st.desc(agent.assignedTaskConfig.description)}`, + `${st.label("Input")}:`, + `${st.input(agent.assignedTaskConfig.input)}`, + ] + : []), + ].join("\n"); + this.agentDetail.setContent(details); + if (shouldRender) { + this.screen.render(); + } } private updateLifecycleHistory(agentId?: string, shouldRender = true): void { @@ -412,10 +445,10 @@ class AgentMonitor { ? events .map( ({ timestamp, event, success, error }) => - `${applyStyle(timestamp, UIConfig.labels.timestamp)} ` + - `${applyStyle(event, UIConfig.labels.eventType)} ` + - `${applyBooleanStyle(success)}` + - (error ? `\n ${applyStyle(error, UIConfig.labels.error)}` : ""), + `${st.timestamp(timestamp)} ` + + `${st.eventType(event)} ` + + `${st.bool(success)}` + + (error ? `\n ${st.error(error)}` : ""), ) .join("\n") : "No lifecycle events recorded"; @@ -436,29 +469,65 @@ class AgentMonitor { } let data; - let poolStats; + let agentIdString; + let agent; + let poolId: AgentPoolId; + let poolIdStr: string; + let poolTypeId: AgentPoolTypeId; + let poolTypeIdStr: string; + let pool: Map | undefined; + let poolType: AgentPool | undefined; switch (update.type) { case AgentUpdateTypeEnum.AGENT_CONFIG: data = update.data as AgentConfig; - this.agentConfigs.set(data.kind, new Map([[data.type, data]])); - this.agentPoolsStats.set( - data.kind, - new Map([ - [data.type, { available: 0, created: 0, inUse: 0, poolSize: data.maxPoolSize }], - ]), - ); - this.updatePoolList(false); + poolId = { agentKind: data.agentKind }; + poolIdStr = agentPoolIdToString(poolId); + poolTypeId = { + agentKind: data.agentKind, + agentType: data.agentType, + } satisfies AgentPoolTypeId; + poolTypeIdStr = agentPoolTypeIdToString(poolTypeId); + pool = this.agentPools.get(poolIdStr); + if (!pool) { + poolType = { + agentPoolTypeId: poolTypeId, + agentConfig: data, + poolStats: { available: 0, created: 0, inUse: 0, poolSize: 0 }, + }; + pool = new Map([[agentPoolTypeIdToString(poolTypeId), poolType]]); + this.agentPools.set(poolIdStr, pool); + } else { + poolType = pool.get(poolTypeIdStr); + if (poolType) { + throw new Error(`PoolType ${JSON.stringify(poolTypeId)} already exists`); + } + poolType = { + agentPoolTypeId: poolTypeId, + agentConfig: data, + poolStats: { available: 0, created: 0, inUse: 0, poolSize: 0 }, + }; + pool.set(poolTypeIdStr, poolType); + } + + this.updateAgentPoolsList(false); break; case AgentUpdateTypeEnum.POOL: data = update.data as PoolChangeData; - poolStats = this.agentPoolsStats.get(data.agentKind)?.get(data.agentType); - if (!poolStats) { + poolTypeId = { agentKind: data.agentKind, agentType: data.agentType }; + poolIdStr = agentPoolIdToString(poolTypeId); + pool = this.agentPools.get(poolIdStr); + if (!pool) { + throw new Error(`Missing pool for agentKind: ${data.agentKind}`); + } + poolTypeIdStr = agentPoolTypeIdToString(poolTypeId); + poolType = pool.get(poolTypeIdStr); + if (!poolType) { throw new Error( - `Missing poolStats for agentKind: ${data.agentKind}, agentType: ${data.agentType}`, + `Missing pool for agentKind: ${data.agentKind} agentType: ${data.agentType}`, ); } - updateDeepPartialObject(poolStats, data); - this.updatePoolList(false); + updateDeepPartialObject(poolType.poolStats, data); + this.updateAgentPoolsList(false); break; case AgentUpdateTypeEnum.AVAILABLE_TOOLS: data = update.data as AvailableToolsData; @@ -469,35 +538,48 @@ class AgentMonitor { .forEach((t) => { this.allAvailableTools.set(t.name, t); }); - this.updatePoolList(false); + this.updateAgentPoolsList(false); break; case AgentUpdateTypeEnum.AGENT: - // case AgentUpdateTypeEnum.AGENT_CONFIG: - // this.agentConfigs.set((update.data as Agent), update.data as AgentConfig); - // break; - // case AgentUpdateTypeEnum.STATUS: - // agent = this.agents.get(update.agentId!) || { - // agentId: update.agentId!, - // type: "", - // kind: "operator", - // inUse: false, - // }; - // Object.assign(agent, update.data); - // this.agents.set(update.agentId!, agent as Agent); - // break; - // case AgentUpdateTypeEnum.LIFECYCLE: - // break; - } + data = update.data as AgentLifecycleData; + agentIdString = agentIdToString(data.agentId); + agent = this.agents.get(agentIdString); + + if (data.event === "onCreate") { + if (agent) { + throw new Error(`Agent ${agentIdString} is already exists`); + } + agent = { + agentId: data.agentId, + inUse: false, + isDestroyed: false, + assignedTaskConfig: null, + history: [], + } satisfies Agent; + this.agents.set(agentIdToString(data.agentId), agent); + this.updateAgentList(); + } else { + if (!agent) { + throw new Error(`Undefined agent ${agentIdString}`); + } + switch (data.event) { + case "onDestroy": + agent.isDestroyed = true; + break; + case "onAcquire": + agent.inUse = true; + agent.assignedTaskConfig = data.taskConfig!; + break; + case "onRelease": + agent.inUse = false; + agent.assignedTaskConfig = null; + agent.history.push(data.historyEntry!); + break; + } + } - // if (this.selectedAgentIndex !== null) { - // const selectedItem = this.agentList.getItem(this.selectedAgentIndex); - // if (selectedItem?.content) { - // const selectedAgentId = selectedItem.content.toString().replace(/\{[^}]+\}/g, ""); - // if (selectedAgentId === update.agentId) { - // this.updateAgentDetails(update.agentId); - // } - // } - // } + break; + } this.logBox.log( `${new Date().toLocaleString()} - Event ${update.type}: update ${JSON.stringify(update.data)}`, diff --git a/src/ui/task-monitor.ts b/src/ui/task-monitor.ts index 2755731..5a141e5 100644 --- a/src/ui/task-monitor.ts +++ b/src/ui/task-monitor.ts @@ -3,7 +3,7 @@ import chokidar from "chokidar"; import { createReadStream } from "fs"; import { join } from "path"; import { createInterface } from "readline"; -import { stringToAgentId } from "src/agents/utils.js"; +import { stringToAgentId } from "src/agents/agent-id.js"; import { TaskConfig, TaskHistoryEntry, diff --git a/src/ui/ui-config.ts b/src/ui/ui-config.ts index 18e8179..509f2cb 100644 --- a/src/ui/ui-config.ts +++ b/src/ui/ui-config.ts @@ -1,10 +1,14 @@ +import { clone } from "remeda"; +import { AgentId, AgentPoolId, AgentPoolTypeId } from "src/agents/agent-id.js"; import { AgentKind, AgentKindSchema, AvailableTool } from "src/agents/agent-registry.js"; -import { AgentId } from "src/agents/utils.js"; import { TaskStatusEnum } from "src/tasks/task-manager.js"; +import { Agent, AgentPool } from "./agent-monitor.js"; export interface StyleItem { fg?: string; bold?: boolean; + + italic?: boolean; icon?: string; } export type StyleItemVersioned = Record; @@ -14,10 +18,12 @@ export type StyleCategory = Record; export const DEFAULT_VERSION = "default"; export const BUSY_IDLE = "busy_idle"; +export const INVERSE_COLOR = "inverse_color"; export const AMBIENT_VERSION = "ambient"; export const UIConfig = { labels: { + default: { fg: "white", bold: true }, taskId: { fg: "#CC5500", bold: true }, status: { fg: "white", bold: true }, agentKind: { fg: "magenta", bold: true }, @@ -26,6 +32,7 @@ export const UIConfig = { [AgentKindSchema.Values.supervisor]: { fg: "#8B4513", bold: true, icon: "⬢" }, [AgentKindSchema.Values.operator]: { fg: "#8B4513", bold: true, icon: "⬡" }, }, + agentPoolId: { fg: "white", italic: true }, owner: { fg: "#8B4513", bold: true }, description: { fg: "#7393B3", bold: false }, input: { fg: "yellow", bold: false }, @@ -72,6 +79,7 @@ export const UIConfig = { boolean: { TRUE: { [DEFAULT_VERSION]: { fg: "green", icon: "[✓]" }, + [INVERSE_COLOR]: { fg: "red", icon: "[✓]" }, [BUSY_IDLE]: { fg: "red", bold: true, @@ -80,6 +88,7 @@ export const UIConfig = { }, FALSE: { [DEFAULT_VERSION]: { fg: "red", icon: "[✕]" }, + [INVERSE_COLOR]: { fg: "green", icon: "[✕]" }, [BUSY_IDLE]: { fg: "green", bold: false, @@ -155,21 +164,25 @@ export function applyNumberStyle(count: number, inverse = false) { export function applyBooleanStyle( value: boolean, - version?: typeof DEFAULT_VERSION | typeof BUSY_IDLE, + version?: typeof DEFAULT_VERSION | typeof BUSY_IDLE | typeof INVERSE_COLOR, ) { const styleVersions = value ? UIConfig.boolean.TRUE : UIConfig.boolean.FALSE; const style = styleVersions[version ?? DEFAULT_VERSION]; return applyStyle(style.icon, { ...style }, version); } -export function applyAgentIdStyle(agentId: AgentId) { - const style = UIConfig.labels.agentId[agentId.agentKind as AgentKind]; - return applyStyle(`${style.icon} ${agentId.agentType}[${agentId.num}]`, { ...style }); +export function applyAgentPoolIdStyle(agentPoolId: AgentPoolId) { + const style = UIConfig.labels.agentPoolId; + return applyStyle(agentPoolId.agentKind, clone(style)); } -export function applyAgentKindTypeStyle(agentKind: AgentKind, agentType: string) { - const style = UIConfig.labels.agentId[agentKind as AgentKind]; - return applyStyle(`${style.icon} ${agentType}`, { ...style }); +export function applyAgentIdStyle(agentId: AgentId | AgentPoolTypeId) { + const style = UIConfig.labels.agentId[agentId.agentKind as AgentKind]; + const isAgentId = (agentId as AgentId).num != null; + return applyStyle( + `${style.icon} ${agentId.agentType}${isAgentId ? `[${(agentId as AgentId).num}]` : ""}`, + { ...style }, + ); } export function applyToolsStyle(tools: AvailableTool[]) { @@ -186,3 +199,64 @@ export function applyToolNameStyle(toolName: string) { const style = UIConfig.labels.tool; return applyStyle(`${style.icon} ${toolName}`, style); } + +export function bool( + value: boolean, + version?: typeof DEFAULT_VERSION | typeof BUSY_IDLE | typeof INVERSE_COLOR, +) { + return applyBooleanStyle(value, version); +} + +export function num(value: number, inverse = false) { + return applyNumberStyle(value, inverse); +} + +export function label(value: string) { + return applyStyle(value, UIConfig.labels.default); +} + +export function agentPoolId(value: AgentPoolId) { + return applyAgentPoolIdStyle(value); +} +export function agentPoolTypeId(value: AgentPoolTypeId) { + return applyAgentIdStyle(value); +} +export function agentId(value: AgentId | AgentPoolTypeId) { + return applyAgentIdStyle(value); +} +export function agentKind(value: string) { + return applyStyle(value, UIConfig.labels.agentKind); +} +export function agentType(value: string) { + return applyStyle(value, UIConfig.labels.agentType); +} +export function taskId(value: string) { + return applyStyle(value, UIConfig.labels.taskId); +} +export function desc(description: string) { + return applyStyle(description, UIConfig.labels.description); +} +export function tools(tools: AvailableTool[]) { + return applyToolsStyle(tools); +} + +export function timestamp(timestamp: string) { + return applyStyle(timestamp, UIConfig.labels.timestamp); +} + +export function eventType(event: string) { + return applyStyle(event, UIConfig.labels.eventType); +} +export function error(error: string) { + return applyStyle(error, UIConfig.labels.error); +} + +export function input(input: string) { + return applyStyle(input, UIConfig.labels.input); +} +export function agentPool(agentPool: AgentPool): string { + return `${agentId(agentPool.agentConfig)} [${num(agentPool.poolStats.available)}/${num(agentPool.poolStats.poolSize)}]`; +} +export function agent(agent: Agent) { + return `${agentId(agent.agentId)} ${bool(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d89dbd8 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "vitest/config"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig({ + plugins: [tsconfigPaths()], + test: { + testTimeout: 60_000 * 5, + setupFiles: ["dotenv/config"], + fileParallelism: false, + // pool: "threads", + // poolOptions: { + // threads: { minThreads: 1, maxThreads: 8 }, + // // forks: { minForks: 7, maxForks: 8 }, + // }, + isolate: false, + retry: 0, + }, +}); diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 0000000..d85a198 --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1,3 @@ +import { defineWorkspace } from "vitest/config"; + +export default defineWorkspace(["./vite.config.ts"]); From 31e24854d7f4f61ee66220047a73c9fcb9f21bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Tue, 18 Feb 2025 02:05:45 +0100 Subject: [PATCH 04/13] feat: add agent and task monitor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- package.json | 5 +- src/access-control/dto.ts | 4 + .../resources-access-control.ts | 196 +++ src/agents/agent-factory.ts | 6 +- src/agents/agent-id.test.ts | 135 -- src/agents/agent-id.ts | 163 ++- src/agents/agent-registry.ts | 619 --------- src/agents/agent-state-logger.ts | 103 -- src/agents/registry/dto.ts | 123 ++ src/agents/registry/registry.ts | 910 +++++++++++++ src/agents/state/builder.ts | 409 ++++++ src/agents/state/dto.ts | 170 +++ src/agents/state/logger.ts | 147 ++ src/agents/supervisor.ts | 91 +- src/agents/tool.ts | 307 ++++- src/base/dto.ts | 3 + src/base/entity-id.ts | 198 +++ src/base/state/base-state-builder.ts | 242 ++++ .../base-state-logger.ts} | 28 +- src/base/state/dto.ts | 14 + src/base/tools-factory.ts | 2 +- src/helpers/llm.ts | 40 +- src/helpers/tmux-logger copy.ts | 92 -- src/helpers/tmux-logger.ts | 105 -- src/{agent.ts => main.ts} | 56 +- src/tasks/manager/dto.ts | 224 ++++ src/tasks/manager/manager.ts | 1182 +++++++++++++++++ src/tasks/state/builder.ts | 297 +++++ src/tasks/state/dto.ts | 115 ++ src/tasks/state/logger.ts | 114 ++ src/tasks/task-id.ts | 158 +++ src/tasks/task-manager.ts | 797 ----------- src/tasks/task-state-logger.ts | 57 - src/tasks/tool.ts | 242 +++- src/ui/agent-monitor.ts | 661 --------- src/ui/agent-monitor/monitor.ts | 590 ++++++++ src/ui/base/monitor.ts | 30 + src/ui/colors.ts | 360 +++++ src/ui/config.ts | 410 ++++++ src/ui/supervisor-monitor.ts | 41 + src/ui/task-monitor.ts | 472 ------- src/ui/task-monitor/monitor.ts | 554 ++++++++ src/ui/ui-config.ts | 262 ---- 43 files changed, 7164 insertions(+), 3570 deletions(-) create mode 100644 src/access-control/dto.ts create mode 100644 src/access-control/resources-access-control.ts delete mode 100644 src/agents/agent-id.test.ts delete mode 100644 src/agents/agent-registry.ts delete mode 100644 src/agents/agent-state-logger.ts create mode 100644 src/agents/registry/dto.ts create mode 100644 src/agents/registry/registry.ts create mode 100644 src/agents/state/builder.ts create mode 100644 src/agents/state/dto.ts create mode 100644 src/agents/state/logger.ts create mode 100644 src/base/dto.ts create mode 100644 src/base/entity-id.ts create mode 100644 src/base/state/base-state-builder.ts rename src/base/{audit-log.ts => state/base-state-logger.ts} (66%) create mode 100644 src/base/state/dto.ts delete mode 100644 src/helpers/tmux-logger copy.ts delete mode 100644 src/helpers/tmux-logger.ts rename src/{agent.ts => main.ts} (69%) create mode 100644 src/tasks/manager/dto.ts create mode 100644 src/tasks/manager/manager.ts create mode 100644 src/tasks/state/builder.ts create mode 100644 src/tasks/state/dto.ts create mode 100644 src/tasks/state/logger.ts create mode 100644 src/tasks/task-id.ts delete mode 100644 src/tasks/task-manager.ts delete mode 100644 src/tasks/task-state-logger.ts delete mode 100644 src/ui/agent-monitor.ts create mode 100644 src/ui/agent-monitor/monitor.ts create mode 100644 src/ui/base/monitor.ts create mode 100644 src/ui/colors.ts create mode 100644 src/ui/config.ts create mode 100644 src/ui/supervisor-monitor.ts delete mode 100644 src/ui/task-monitor.ts create mode 100644 src/ui/task-monitor/monitor.ts delete mode 100644 src/ui/ui-config.ts diff --git a/package.json b/package.json index 9140386..976fa8a 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,8 @@ "scripts": { "start": "NODE_ENV=development npm run startx", "startx": "chmod +x ./start.sh && ./start.sh", - "start:dev": "tsx --no-warnings src/agent.js", - "task:monitor": "tsx --no-warnings src/ui/task-monitor.js", - "agent:monitor": "tsx --inspect --no-warnings src/ui/agent-monitor.js", + "start:dev": "tsx --inspect --no-warnings src/main.js", + "monitor": "tsx --no-warnings src/ui/supervisor-monitor.js", "ts:check": "tsc --noEmit --project tsconfig.json", "build": "rimraf dist && tsc", "lint": "eslint", diff --git a/src/access-control/dto.ts b/src/access-control/dto.ts new file mode 100644 index 0000000..c2641a6 --- /dev/null +++ b/src/access-control/dto.ts @@ -0,0 +1,4 @@ +import { z } from "zod"; + +export const PermissionSchema = z.enum(["read", "write", "execute"]); +export type Permission = z.infer; diff --git a/src/access-control/resources-access-control.ts b/src/access-control/resources-access-control.ts new file mode 100644 index 0000000..efef064 --- /dev/null +++ b/src/access-control/resources-access-control.ts @@ -0,0 +1,196 @@ +import { clone, difference } from "remeda"; +import { Permission } from "./dto.js"; +import { Logger } from "bee-agent-framework"; + +export type UserId = string; +export type ResourceId = string; + +export interface ResourcePermissions { + userPermissions: Map; + ownerId: UserId; +} +export interface ResourcesPermissionRegistry { + resources: Map; + admins: Set; +} + +export const ADMIN_USER = "@admin"; +export const REGISTRY_RESOURCE = "@registry"; +export const RESERVED_IDS = [ADMIN_USER, REGISTRY_RESOURCE]; + +export const FULL_ACCESS: Permission[] = ["read", "write", "execute"] as const; +export const READ_ONLY_ACCESS: Permission[] = ["read"] as const; +export const WRITE_ONLY_ACCESS: Permission[] = ["write"] as const; +export const READ_WRITE_ACCESS: Permission[] = ["read", "write"] as const; +export const READ_EXECUTE_ACCESS: Permission[] = ["read", "execute"] as const; + +export class PermissionError extends Error { + constructor( + public userId: string, + public requiredPermission: Permission, + public resourceOwnerId: string, + ) { + super( + `User ${userId} does not have ${requiredPermission} permission for resource owned by ${resourceOwnerId}`, + ); + this.name = "PermissionError"; + } +} + +export class ResourcesAccessControl { + private readonly logger: Logger; + private registry: ResourcesPermissionRegistry = { + admins: new Set(), + resources: new Map([[REGISTRY_RESOURCE, { ownerId: ADMIN_USER, userPermissions: new Map() }]]), + }; + + constructor(entityName: string, admins: UserId[] = []) { + this.logger = Logger.root.child({ name: `${entityName}AccessControl` }); + admins.forEach((adminId) => this.registry.admins.add(adminId)); + } + + private checkReservedConstants(...requestedIds: (UserId | ResourceId)[]) { + RESERVED_IDS.forEach((id) => { + requestedIds.forEach((requestedId) => { + if (requestedId === id) { + throw new Error(`${requestedId} is reserved for system purpose please chose another`); + } + }); + }); + } + + private getResourcePermissions(resourceId: ResourceId, throwError = true) { + const resourcePermissions = this.registry.resources.get(resourceId); + if (!resourcePermissions && throwError) { + throw new Error(`Resource permissions for resourceId:${resourceId} was not found`); + } + return resourcePermissions; + } + + private destroyResourcePermissions(resourceId: ResourceId, throwError = true) { + this.logger.info({ resourceId }, `destroyResourcePermissions`); + const resourcePermissions = this.registry.resources.has(resourceId); + if (!resourcePermissions && throwError) { + throw new Error( + `Resource permissions for resourceId:${resourceId} was not found for destruction`, + ); + } + this.registry.resources.delete(resourceId); + } + + private isAdmin(userId: UserId) { + return this.registry.admins.has(userId); + } + + createPermissions( + resourceId: ResourceId, + userId: UserId, + permissions: Permission[], + actingUserId: UserId, + ) { + this.logger.info({ resourceId, userId, permissions, actingUserId }, `createPermissions`); + this.checkReservedConstants(resourceId, userId); + this.checkPermission(resourceId, actingUserId, ["write"]); + + const resourcePermissions = this.getResourcePermissions(resourceId)!; + resourcePermissions.userPermissions.set(userId, clone(permissions)); + } + + removePermissions( + resourceId: ResourceId, + userId: UserId, + actingUserId: UserId, + permissions?: Permission[], + ) { + this.logger.info({ resourceId, userId, permissions, actingUserId }, `removePermissions`); + this.checkPermission(resourceId, actingUserId, WRITE_ONLY_ACCESS); + + const resourcePermissions = this.getResourcePermissions(resourceId)!; + const userPermissions = resourcePermissions.userPermissions.get(userId); + if (!userPermissions) { + throw new Error(`User permissions for userId:${userId} was not found`); + } + + if (permissions == null) { + resourcePermissions.userPermissions.delete(userId); + } else { + const newPermissions = difference(permissions, userPermissions); + if (!newPermissions.length) { + resourcePermissions.userPermissions.delete(userId); + } else { + resourcePermissions.userPermissions.set(userId, clone(permissions)); + } + } + } + + createResource( + resourceId: ResourceId, + ownerId: UserId, + actingUserId: UserId, + parentResourceId?: ResourceId, + ) { + this.logger.info({ resourceId, ownerId, actingUserId, parentResourceId }, `createResource`); + this.checkReservedConstants(resourceId, ownerId); + if (parentResourceId) { + this.checkPermission(parentResourceId, actingUserId, WRITE_ONLY_ACCESS, false); + } + + let resourcePermissions = this.getResourcePermissions(resourceId, false); + if (resourcePermissions) { + throw new Error(`Resource permissions for resourceId: ${resourceId} already exist`); + } + resourcePermissions = { ownerId, userPermissions: new Map() } satisfies ResourcePermissions; + this.registry.resources.set(resourceId, resourcePermissions); + } + + removeResource(resourceId: ResourceId, actingUserId: UserId) { + this.logger.info({ resourceId, actingUserId }, `removeResource`); + this.checkPermission(resourceId, actingUserId, WRITE_ONLY_ACCESS, false); + this.destroyResourcePermissions(resourceId); + } + + checkPermission( + resourceId: ResourceId, + userId: UserId, + requestedPermissions: Permission[], + throwError = true, + ) { + const resourcePermissions = this.getResourcePermissions(resourceId, throwError)!; + + if (this.isAdmin(userId)) { + // He is an admin + return; + } + + if (resourcePermissions.ownerId === userId) { + // He is an owner + return; + } + + requestedPermissions.forEach((permission) => { + const userResourcePermissions = resourcePermissions.userPermissions.get(userId); + if (!userResourcePermissions || !userResourcePermissions.includes(permission)) { + throw new PermissionError(userId, permission, resourcePermissions.ownerId); + } + }); + } + + /** + * Safe version that returns boolean instead of throwing + */ + hasPermission( + resourceId: ResourceId, + userId: UserId, + requestedPermissions: Permission[], + ): boolean { + try { + this.checkPermission(resourceId, userId, requestedPermissions); + return true; + } catch (error) { + if (error instanceof PermissionError) { + return false; + } + throw error; + } + } +} diff --git a/src/agents/agent-factory.ts b/src/agents/agent-factory.ts index 27850f4..1b1c15c 100644 --- a/src/agents/agent-factory.ts +++ b/src/agents/agent-factory.ts @@ -1,13 +1,13 @@ import { getChatLLM } from "src/helpers/llm.js"; -import { AgentKind } from "./agent-registry.js"; import * as supervisor from "./supervisor.js"; import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; import { UnconstrainedMemory } from "bee-agent-framework/memory/unconstrainedMemory"; import { TokenMemory } from "bee-agent-framework/memory/tokenMemory"; import { BaseToolsFactory } from "src/base/tools-factory.js"; +import { AgentKindEnum } from "./registry/dto.js"; export interface BaseCreateAgentInput { - agentKind: AgentKind; + agentKind: AgentKindEnum; agentType: string; agentId: string; instructions: string; @@ -54,5 +54,7 @@ export function createAgent( }), }, }); + default: + throw new Error(`Undefined agent kind agentKind:${input.agentKind}`); } } diff --git a/src/agents/agent-id.test.ts b/src/agents/agent-id.test.ts deleted file mode 100644 index ec56ac4..0000000 --- a/src/agents/agent-id.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - AgentId, - AgentPoolTypeId, - agentIdToString, - agentPoolTypeIdToString, - stringToAgentId, - stringAgentIdToAgentPoolTypeId, -} from "./agent-id.js"; -import { AgentKindSchema } from "./agent-registry.js"; - -describe("Agent ID Functions", () => { - describe("agentPoolIdToString", () => { - it("should convert AgentPoolId to string format", () => { - const agentPoolId: AgentPoolTypeId = { - agentKind: AgentKindSchema.Values.operator, - agentType: "worker", - }; - - expect(agentPoolTypeIdToString(agentPoolId)).toBe("operator:worker"); - }); - - it("should handle different agent kinds and types", () => { - const cases: [AgentPoolTypeId, string][] = [ - [{ agentKind: AgentKindSchema.Values.operator, agentType: "test" }, "operator:test"], - [{ agentKind: AgentKindSchema.Values.supervisor, agentType: "api" }, "supervisor:api"], - [ - { agentKind: AgentKindSchema.Values.operator, agentType: "special-type" }, - "operator:special-type", - ], - ]; - - cases.forEach(([input, expected]) => { - expect(agentPoolTypeIdToString(input)).toBe(expected); - }); - }); - }); - - describe("stringToAgentPoolId", () => { - it("should convert string to AgentPoolId", () => { - const result = stringAgentIdToAgentPoolTypeId("operator:worker"); - - expect(result).toEqual({ - agentKind: AgentKindSchema.Values.operator, - agentType: "worker", - }); - }); - - it("should handle strings with brackets", () => { - const result = stringAgentIdToAgentPoolTypeId("operator:worker[1]"); - - expect(result).toEqual({ - agentKind: AgentKindSchema.Values.operator, - agentType: "worker", - }); - }); - - it("should handle different formats", () => { - const cases = [ - ["supervisor:api", { agentKind: AgentKindSchema.Values.supervisor, agentType: "api" }], - ["operator:test[5]", { agentKind: AgentKindSchema.Values.operator, agentType: "test" }], - ]; - - cases.forEach(([input, expected]) => { - expect(stringAgentIdToAgentPoolTypeId(input as string)).toEqual(expected); - }); - }); - }); - - describe("agentIdToString", () => { - it("should convert AgentId to string format", () => { - const agentId: AgentId = { - agentKind: AgentKindSchema.Values.operator, - agentType: "worker", - num: 1, - }; - - expect(agentIdToString(agentId)).toBe("operator:worker[1]"); - }); - - it("should handle different agent numbers", () => { - const cases: [AgentId, string][] = [ - [ - { agentKind: AgentKindSchema.Values.operator, agentType: "test", num: 0 }, - "operator:test[0]", - ], - [ - { agentKind: AgentKindSchema.Values.supervisor, agentType: "api", num: 999 }, - "supervisor:api[999]", - ], - [ - { agentKind: AgentKindSchema.Values.operator, agentType: "worker", num: -1 }, - "operator:worker[-1]", - ], - ]; - - cases.forEach(([input, expected]) => { - expect(agentIdToString(input)).toBe(expected); - }); - }); - }); - - describe("stringToAgentId", () => { - it("should convert string to AgentId", () => { - const result = stringToAgentId("operator:worker[1]"); - - expect(result).toEqual({ - agentKind: AgentKindSchema.Values.operator, - agentType: "worker", - num: 1, - }); - }); - - it("should handle various formats", () => { - const cases = [ - [ - "supervisor:api[5]", - { agentKind: AgentKindSchema.Values.supervisor, agentType: "api", num: 5 }, - ], - [ - `operator:test[-1]`, - { agentKind: AgentKindSchema.Values.operator, agentType: "test", num: -1 }, - ], - [ - "operator:worker[0]", - { agentKind: AgentKindSchema.Values.operator, agentType: "worker", num: 0 }, - ], - ]; - - cases.forEach(([input, expected]) => { - expect(stringToAgentId(input as string)).toEqual(expected); - }); - }); - }); -}); diff --git a/src/agents/agent-id.ts b/src/agents/agent-id.ts index b81dfb7..08ebf50 100644 --- a/src/agents/agent-id.ts +++ b/src/agents/agent-id.ts @@ -1,64 +1,161 @@ -import { AgentKind } from "./agent-registry.js"; +import { + EntityKindId, + EntityNumId, + entityToKindString, + entityToTypeIdString, + entityToVersionIdString, + entityToVersionNumIdString, + EntityTypeId, + EntityVersionId, + EntityVersionNumId, + stringToEntityKind, + stringToEntityType, + stringToEntityVersion, + stringToEntityVersionNum, +} from "src/base/entity-id.js"; +import { + AgentConfigIdValue, + AgentConfigVersionValue, + AgentIdValue, + AgentKindEnum, + AgentKindEnumSchema, + AgentKindValue, + AgentNumValue, + AgentTypeValue, +} from "./registry/dto.js"; -export interface AgentId extends AgentPoolTypeId { - num: number; +// Agent specific interfaces with domain-specific naming +export interface AgentKindId { + agentKind: AgentKindEnum; } -export interface AgentPoolTypeId extends AgentPoolId { - agentType: string; +export interface AgentTypeId extends AgentKindId { + agentType: AgentTypeValue; } -export interface AgentPoolId { - agentKind: AgentKind; +export interface AgentConfigId extends AgentTypeId { + agentConfigVersion: AgentConfigVersionValue; } -export function agentPoolIdToString(agentPoolId: AgentPoolId) { - return `${agentPoolId.agentKind}`; +export interface AgentId extends AgentTypeId { + agentNum: AgentNumValue; + agentConfigVersion: AgentConfigVersionValue; } -export function agentPoolTypeIdToString(agentPoolId: AgentPoolTypeId) { - return `${agentPoolIdToString(agentPoolId)}:${agentPoolId.agentType}`; +// Public conversion functions to generic types +export function agentKindToEntityKindId(agentKindId: AgentKindId): EntityKindId { + return { + kind: agentKindId.agentKind, + }; } -export function stringToAgentPoolId(agentPoolId: string): AgentPoolId { +export function agentTypeToEntityTypeId(agentTypeId: AgentTypeId): EntityTypeId { return { - agentKind: agentPoolId as AgentKind, + ...agentKindToEntityKindId(agentTypeId), + type: agentTypeId.agentType, }; } -export function stringToAgentPoolTypeId(agentPoolTypeId: string): AgentPoolTypeId { - const [kind, rest] = agentPoolTypeId.split(":"); - +export function agentConfigToEntityVersionId( + agentConfigId: AgentConfigId, +): EntityVersionId { return { - agentKind: kind as AgentKind, - agentType: rest, + ...agentTypeToEntityTypeId(agentConfigId), + version: agentConfigId.agentConfigVersion, }; } -export function stringAgentIdToAgentPoolTypeId(agentPoolId: string): AgentPoolTypeId { - const [kind, rest] = agentPoolId.split(":"); - const [type] = rest.split("["); +export function agentToEntityNumId(agentId: AgentId): EntityNumId { + return { + ...agentTypeToEntityTypeId(agentId), + num: agentId.agentNum, + }; +} +export function agentToEntityVersionNumId(agentId: AgentId): EntityVersionNumId { return { - agentKind: kind as AgentKind, - agentType: type, + ...agentToEntityNumId(agentId), + version: agentId.agentConfigVersion, }; } -export function agentIdToString(agentId: AgentId) { - return `${agentPoolTypeIdToString(agentId)}[${agentId.num}]`; +// Agent ID validation +function validateAgentKind(kind: string): AgentKindEnum { + const result = AgentKindEnumSchema.safeParse(kind); + if (!result.success) { + throw new Error(`Invalid agent kind: ${kind}`); + } + return result.data; } -export function stringToAgentId(agentId: string): AgentId { - const agentPoolId = stringAgentIdToAgentPoolTypeId(agentId); - const num = parseInt((agentId.match(/\[(.*?)\]/) || [])[1]); +// Agent conversion functions from string +export function stringToAgentKind(str: string): AgentKindId { + const generic = stringToEntityKind(str, validateAgentKind); + return { + agentKind: generic.kind, + }; +} - if (num == null) { - throw new Error(`AgentId ${agentId} valid num is not presence`); - } +export function stringToAgentType(str: string): AgentTypeId { + const generic = stringToEntityType(str, validateAgentKind); + return { + agentKind: generic.kind, + agentType: generic.type, + }; +} +export function stringToAgentConfig(str: string): AgentConfigId { + const generic = stringToEntityVersion(str, validateAgentKind); return { - ...agentPoolId, - num, + agentKind: generic.kind, + agentType: generic.type, + agentConfigVersion: generic.version, }; } + +export function stringToAgent(str: string): AgentId { + const generic = stringToEntityVersionNum(str, validateAgentKind); + return { + agentKind: generic.kind, + agentType: generic.type, + agentNum: generic.num, + agentConfigVersion: generic.version, + }; +} + +// String conversion functions +export function agentSomeIdToKindValue( + agentSomeId: AgentKindId | AgentTypeId | AgentConfigId | AgentId, +): AgentKindValue { + return entityToKindString(agentKindToEntityKindId(agentSomeId)); +} + +export function agentSomeIdToTypeValue( + agentSomeId: AgentTypeId | AgentConfigId | AgentId, +): AgentTypeValue { + return entityToTypeIdString(agentTypeToEntityTypeId(agentSomeId)); +} + +export function agentConfigIdToValue(agentConfigId: AgentConfigId): AgentConfigIdValue { + return entityToVersionIdString(agentConfigToEntityVersionId(agentConfigId)); +} + +export function agentIdToString(agentId: AgentId): AgentIdValue { + return entityToVersionNumIdString(agentToEntityVersionNumId(agentId)); +} + +// Generic conversion that handles any agent ID type +export function agentSomeIdToString( + agentSomeId: AgentKindId | AgentTypeId | AgentConfigId | AgentId, +): string { + if ("num" in agentSomeId) { + return agentIdToString(agentSomeId as AgentId); + } + if ("version" in agentSomeId) { + return agentConfigIdToValue(agentSomeId as AgentConfigId); + } + if ("agentType" in agentSomeId) { + return agentSomeIdToTypeValue(agentSomeId as AgentTypeId); + } + return agentSomeIdToKindValue(agentSomeId); +} diff --git a/src/agents/agent-registry.ts b/src/agents/agent-registry.ts deleted file mode 100644 index b9a8f8f..0000000 --- a/src/agents/agent-registry.ts +++ /dev/null @@ -1,619 +0,0 @@ -import { Logger } from "bee-agent-framework/logger/logger"; -import { BaseToolsFactory } from "src/base/tools-factory.js"; -import { z } from "zod"; -import { getAgentStateLogger } from "./agent-state-logger.js"; -import { stringToAgentId } from "./agent-id.js"; - -export const AgentKindSchema = z - .enum(["supervisor", "operator"]) - .describe( - "Specifies the role type of an agent in the system. A 'supervisor' has administrative privileges and can oversee multiple operators, while an 'operator' handles day-to-day operational tasks.", - ); -export type AgentKind = z.infer; - -/** - * Schema for configuring an agent type. - * Defines the basic properties and requirements for creating agents of a specific type. - */ -export const AgentConfigSchema = z.object({ - agentKind: AgentKindSchema, - agentType: z.string().describe("Unique identifier for the agent type"), - instructions: z.string().describe("Provide detailed instructions on how the agent should act."), - description: z - .string() - .describe("Description of the agent's behavior and purpose of his existence."), - tools: z - .array(z.string()) - .nullish() - .describe( - "List of tool identifiers that this agent type can utilize. Null/undefined means all available. Empty array means no tools.", - ), - maxPoolSize: z - .number() - .int() - .min(0) - .default(0) - .describe("Maximum number of agents to maintain in the pool for this type."), - autoPopulatePool: z - .boolean() - .default(false) - .describe("Populates the agent pool for a specific type up to its configured size."), -}); -export type AgentConfig = z.infer; - -/** - * Schema for an individual agent instance. - * Represents a specific instance of an agent with its runtime state. - */ -export const AgentSchema = z.object({ - /** Unique identifier for this specific agent instance */ - agentId: z.string(), - /** The type of agent this instance represents */ - agentType: z.string(), - agentKind: AgentKindSchema, - /** Configuration settings for this agent */ - config: AgentConfigSchema, - /** - * Indicates whether this agent is currently being used - * Used for pool management to track available agents - */ - inUse: z.boolean().default(false), - instance: z.any(), -}); -export type Agent = z.infer; -export type AgentWithInstance = Omit & { - instance: TAgentInstance; -}; - -/** - * Schema for an available tool. - */ -export const AvailableToolSchema = z.object({ - name: z.string(), - description: z.string(), -}); - -export type AvailableTool = z.infer; - -/** - * Schema for pool statistics of an agent type - * Provides information about pool capacity and utilization - */ -export const PoolStatsSchema = z - .object({ - /** Maximum number of agents that can be in the pool */ - poolSize: z.number(), - /** Number of agents currently available in the pool */ - available: z.number(), - /** Number of agents currently in use from the pool */ - inUse: z.number(), - /** Number of created agents */ - created: z.number(), - }) - .describe("Statistics about an agent type's pool"); - -export type PoolStats = z.infer; - -/** - * Callbacks for managing agent lifecycle events. - * These callbacks allow customization of agent behavior at key points in their lifecycle. - */ -export interface AgentLifecycleCallbacks { - /** - * Called when a new agent needs to be created - * @param config - Configuration for the agent - * @param poolStats - Statistics of the agent pool - * @param toolsFactory - Factory to create tools - * @returns Promise resolving to the new agent's id and instance - */ - onCreate: ( - config: AgentConfig, - poolStats: PoolStats, - toolsFactory: BaseToolsFactory, - ) => Promise<{ agentId: string; instance: TAgentInstance }>; - - /** - * Called when an agent is being destroyed - * @param instance - Instance of the agent being destroyed - */ - onDestroy: (instance: TAgentInstance) => Promise; - - /** - * Optional callback when an agent is acquired from the pool - * Use this to prepare an agent for reuse - * @param agentId - ID of the agent being acquired - */ - onAcquire?: (agentId: string) => Promise; - - /** - * Optional callback when an agent is released back to the pool - * Use this to clean up agent state before returning to pool - * @param agentId - ID of the agent being released - */ - onRelease?: (agentId: string) => Promise; -} - -type AgentTypePoolMap = Map>; -type AgentConfigMap = Map; -export interface AgentInstanceRef { - agentId: string; - instance: TInstance; -} - -export type AgentTypesMap = Map>; - -/** - * Registry for managing agent types, instances, and pools. - * Provides functionality for: - * - Registering and managing agent types - * - Creating and destroying agent instances - * - Managing pools of reusable agents - * - Tracking agent lifecycle and utilization - * - * Usage: - * - Whenever you need an agent first look if there already is an suitable agent type if not let register new one. - * - */ -export class AgentRegistry { - private readonly logger: Logger; - /** Map of registered agent kind and their configurations */ - private agentConfigs: Map; - /** Map of all agent instances */ - private agents = new Map(); - /** Map of agent pools by kind and type, containing sets of available agent IDs */ - private agentPools: Map; - /** Callbacks for agent lifecycle events */ - private lifecycleCallbacks: AgentLifecycleCallbacks; - private onAgentTypeRegistered: (agentKind: AgentKind, agentType: string) => void; - /** Maps of tools factories for use by agents per agent kinds */ - private toolsFactory = new Map(); - - /** - * Creates a new AgentRegistry instance - * @param callbacks - Callbacks for handling agent lifecycle events and agent type registration - */ - constructor({ - agentLifecycle, - onAgentTypeRegistered, - }: { - onAgentTypeRegistered: (agentKind: AgentKind, agentType: string) => void; - agentLifecycle: AgentLifecycleCallbacks; - }) { - this.logger = Logger.root.child({ name: "AgentRegistry" }); - this.logger.info("Initializing AgentRegistry"); - this.lifecycleCallbacks = agentLifecycle; - this.onAgentTypeRegistered = onAgentTypeRegistered; - // Initialize agent pools for all agent kinds - this.agentConfigs = new Map(AgentKindSchema.options.map((kind) => [kind, new Map()])); - this.agentPools = new Map(AgentKindSchema.options.map((kind) => [kind, new Map()])); - } - - /** - * Register tools factory for a specific agent type - * @param tuples - */ - registerToolsFactories(tuples: [AgentKind, BaseToolsFactory][]) { - tuples.map(([kind, factory]) => { - this.toolsFactory.set(kind, factory); - getAgentStateLogger().logAvailableTools(kind, factory.getAvailableTools()); - }); - } - - private getAgentKindPoolMap(kind: AgentKind) { - const poolKind = this.agentPools.get(kind); - if (!poolKind) { - throw new Error(`There is missing pool for agent kind:${kind}`); - } - return poolKind; - } - - private getAgentPoolMap(kind: AgentKind, type: string) { - const poolKind = this.getAgentKindPoolMap(kind); - const pool = poolKind.get(type); - if (!poolKind) { - throw new Error(`There is missing pool for agent kind:${kind} type:${type}`); - } - return pool; - } - - private getAgentConfigMap(kind: AgentKind) { - const typesMap = this.agentConfigs.get(kind); - if (!typesMap) { - throw new Error(`There is missing types map for agent kind:${kind}`); - } - return typesMap; - } - - getToolsFactory(agentKind: AgentKind): BaseToolsFactory { - const factory = this.toolsFactory.get(agentKind); - if (!factory) { - this.logger.error(`There is missing tools factory for the '${agentKind}' agent kind.`, { - agentKind, - }); - throw new Error(`There is missing tools factory for the '${agentKind}' agent kind`); - } - - return factory; - } - - /** - * Registers a new agent type with the registry - * If the agent type has a pool size > 0, initializes and populates the pool - * @param config - Configuration for the new agent type - * @throws Error if agent type is already registered - */ - registerAgentType(config: AgentConfig): void { - const { agentKind: kind, agentType: type, maxPoolSize, autoPopulatePool } = config; - this.logger.info("Registering new agent type", { - kind: kind, - type: type, - poolSize: maxPoolSize, - }); - - const agentTypesMap = this.getAgentConfigMap(kind); - if (agentTypesMap.has(type)) { - this.logger.error("Agent type already registered", { type: type }); - throw new Error(`Agent type '${type}' is already registered`); - } - - const toolsFactory = this.getToolsFactory(config.agentKind); - const availableTools = toolsFactory.getAvailableTools(); - if (config.tools?.filter((it) => !!it.length).length) { - const undefinedTools = config.tools.filter( - (tool) => !availableTools.some((at) => at.name === tool), - ); - if (undefinedTools.length) { - this.logger.error(`Tool wasn't found between available tools `, { - availableTools: availableTools.map((at) => at.name), - undefinedTools, - }); - throw new Error( - `Tools [${undefinedTools.join(",")}] weren't found between available tools [${availableTools.map((at) => at.name).join(",")}]`, - ); - } - } else { - config.tools = toolsFactory.getAvailableToolsNames(); - } - - agentTypesMap.set(type, config); - getAgentStateLogger().logAgentConfigCreate(config); - - // Initialize pool if pooling is enabled - if (maxPoolSize > 0) { - this.logger.debug("Initializing agent pool", { - kind, - type, - poolSize: maxPoolSize, - }); - - const kindPool = this.getAgentKindPoolMap(kind); - kindPool.set(type, new Set([])); - - if (autoPopulatePool) { - // Pre-populate pool - this.populatePool(kind, type).catch((error) => { - this.logger.error("Failed to populate pool", { type: type, error }); - }); - } - } - - this.onAgentTypeRegistered(kind, type); - } - - /** - * Populates the agent pool for a specific type up to its configured size - * @param type - The agent type to populate pool for - * @private - */ - private async populatePool(kind: AgentKind, type: string): Promise { - this.logger.debug("Populating agent pool", { type }); - const config = this.getAgentTypeConfig(kind, type); - const pool = this.getAgentPoolMap(kind, type); - - if (!pool || config.maxPoolSize <= 0) { - this.logger.trace("Pool population skipped - no pool or size 0", { type }); - return; - } - - const currentPoolSize = pool.size; - const needed = config.maxPoolSize - currentPoolSize; - - this.logger.debug("Creating agents for pool", { - type, - needed, - currentPoolSize, - targetSize: config.maxPoolSize, - }); - - for (let i = 0; i < needed; i++) { - const agent = await this.createAgent(kind, type, true); - const { agentId } = agent; - pool.add(agentId); - this.logger.trace("Added agent to pool", { kind, type, agentId }); - } - } - - /** - * Returns list of all registered agent types - * @returns Array of agent type identifiers - */ - getAgentTypes(): string[] { - this.logger.trace("Getting registered agent types"); - return Array.from(this.agentConfigs.keys()); - } - - /** - * Retrieves configuration for a specific agent type - * @param kind - The agent kind to get configuration for - * @param type - The agent type to get configuration for - * @returns Configuration for the specified agent type - * @throws Error if agent type is not registered - */ - getAgentTypeConfig(kind: AgentKind, type: string): AgentConfig { - this.logger.trace("Getting agent type configuration", { type }); - const config = this.getAgentConfigMap(kind).get(type); - if (!config) { - this.logger.error("Agent type not found", { type }); - throw new Error(`Agent type '${type}' not found`); - } - return config; - } - - /** - * Acquires an agent instance from the pool or creates a new one - * @param kind - The kind of agent to acquire - * @param type - The type of agent to acquire - * @returns Promise resolving to the agent ID - * @throws Error if no agents are available and pool is at capacity - */ - async acquireAgent(kind: AgentKind, type: string): Promise> { - this.logger.debug("Attempting to acquire agent", { type }); - const config = this.getAgentTypeConfig(kind, type); - const pool = this.getAgentPoolMap(kind, type); - - if (!pool || config.maxPoolSize === 0) { - this.logger.debug("No pool available, creating new agent", { type }); - return this._acquireAgent(await this.createAgent(kind, type, false)); - } - - // Try to get an available agent from the pool - for (const agentId of pool) { - const agent = this.agents.get(agentId); - if (agent && !agent.inUse) { - pool.delete(agentId); - agent.inUse = true; - - this.logger.debug("Acquired agent from pool", { type, agentId }); - return this._acquireAgent(agent); - } - } - - // No available agents in pool - if (pool.size < config.maxPoolSize) { - this.logger.debug("Pool not at capacity, creating new agent", { - kind, - type, - currentSize: pool.size, - maxSize: config.maxPoolSize, - }); - return this._acquireAgent(await this.createAgent(kind, type, false)); - } - - this.logger.error("No available agents and pool at capacity", { - type, - poolSize: config.maxPoolSize, - }); - throw new Error(`No available agents of type '${type}' in pool and pool is at capacity`); - } - - private async _acquireAgent(agent: Agent) { - const { agentId } = agent; - if (this.lifecycleCallbacks.onAcquire) { - this.logger.trace("Executing onAcquire callback", { agentId }); - await this.lifecycleCallbacks.onAcquire(agentId); - } - getAgentStateLogger().logAgentLifeCycle({ - event: "onAcquire", - agentId: stringToAgentId(agent.agentId), - }); - - const poolStats = this.getPoolStats(agent.agentKind, agent.agentType); - getAgentStateLogger().logPoolChange({ - agentKind: agent.agentKind, - agentType: agent.agentType, - ...poolStats, - }); - return agent as AgentWithInstance; - } - - /** - * Releases an agent back to its pool or destroys it - * @param agentId - ID of the agent to release - * @throws Error if agent is not found - */ - async releaseAgent(agentId: string): Promise { - this.logger.debug("Attempting to release agent", { agentId }); - const agent = this.agents.get(agentId); - if (!agent) { - this.logger.error("Agent not found for release", { agentId }); - throw new Error(`Agent with ID '${agentId}' not found`); - } - - const { - agentKind: kind, - agentType: type, - maxPoolSize, - } = this.getAgentTypeConfig(agent.agentKind, agent.agentType); - const pool = this.getAgentPoolMap(kind, type); - - if (!pool || maxPoolSize === 0) { - this.logger.debug("No pool available, destroying agent", { agentId }); - await this.destroyAgent(agentId); - return; - } - - if (this.lifecycleCallbacks.onRelease) { - this.logger.trace("Executing onRelease callback", { agentId }); - await this.lifecycleCallbacks.onRelease(agentId); - } - - // Return to pool - agent.inUse = false; - pool.add(agentId); - this.logger.debug("Agent released back to pool", { agentId, type: agent.agentType }); - getAgentStateLogger().logAgentLifeCycle({ - event: "onRelease", - agentId: stringToAgentId(agent.agentId), - }); - - const poolStats = this.getPoolStats(agent.agentKind, agent.agentType); - getAgentStateLogger().logPoolChange({ - agentKind: agent.agentKind, - agentType: agent.agentType, - ...poolStats, - }); - } - - /** - * Creates a new agent instance - * @param type - The type of agent to create - * @param forPool - Whether this agent is being created for a pool - * @returns Promise resolving to the new agent's ID - * @private - */ - private async createAgent( - kind: AgentKind, - type: string, - forPool: boolean, - ): Promise> { - this.logger.debug("Creating new agent", { kind, type, forPool }); - const config = this.getAgentTypeConfig(kind, type); - let poolStats = this.getPoolStats(kind, type); - const toolsFactory = this.getToolsFactory(kind); - const { agentId, instance } = await this.lifecycleCallbacks.onCreate( - config, - poolStats, - toolsFactory, - ); - - const agent = { - agentId, - agentKind: kind, - agentType: type, - config, - inUse: !forPool, - instance, - } satisfies Agent; - this.agents.set(agentId, agent); - - this.logger.info("Agent created successfully", { agentId, type, forPool }); - - getAgentStateLogger().logAgentLifeCycle({ - event: "onCreate", - agentId: stringToAgentId(agentId), - }); - - poolStats = this.getPoolStats(kind, type); - getAgentStateLogger().logPoolChange({ agentKind: kind, agentType: type, ...poolStats }); - return agent; - } - - /** - * Destroys an existing agent instance - * @param agentId - ID of the agent to destroy - * @throws Error if agent is not found - */ - async destroyAgent(agentId: string): Promise { - this.logger.debug("Attempting to destroy agent", { agentId }); - const agent = this.agents.get(agentId); - if (!agent) { - this.logger.error("Agent not found for destruction", { agentId }); - throw new Error(`Agent with ID '${agentId}' not found`); - } - - // Remove from pool if it's in one - const pool = this.getAgentPoolMap(agent.agentKind, agent.agentType); - if (pool) { - pool.delete(agentId); - this.logger.trace("Removed agent from pool", { - agentId, - kind: agent.agentKind, - type: agent.agentType, - }); - } else { - throw new Error(`Missing pool`); - } - - await this.lifecycleCallbacks.onDestroy(agent.instance); - this.agents.delete(agentId); - this.logger.info("Agent destroyed successfully", { - agentId, - kind: agent.agentKind, - type: agent.agentType, - }); - - getAgentStateLogger().logAgentLifeCycle({ - event: "onDestroy", - agentId: stringToAgentId(agentId), - }); - - const poolStats = this.getPoolStats(agent.agentKind, agent.agentType); - getAgentStateLogger().logPoolChange({ - agentKind: agent.agentKind, - agentType: agent.agentType, - ...poolStats, - }); - } - - /** - * Returns list of all active agent instances - * @returns Array of active agents - */ - getActiveAgents(): Agent[] { - this.logger.trace("Getting active agents"); - return Array.from(this.agents.values()).filter((a) => a.inUse); - } - - /** - * Retrieves a specific agent instance by ID - * @param agentId - ID of the agent to retrieve - * @returns The requested agent instance - * @throws Error if agent is not found - */ - getAgent(agentId: string): Agent { - this.logger.trace("Getting agent by ID", { agentId }); - const agent = this.agents.get(agentId); - if (!agent) { - this.logger.error("Agent not found", { agentId }); - throw new Error(`Agent with ID '${agentId}' not found`); - } - return agent; - } - - /** - * Returns statistics about the agent pool for a specific type - * @param type - The agent type to get pool statistics for - * @returns Object containing pool statistics - */ - getPoolStats(kind: AgentKind, type: string): PoolStats { - this.logger.trace("Getting pool statistics", { type }); - const config = this.getAgentTypeConfig(kind, type); - const pool = this.getAgentPoolMap(kind, type); - - if (!pool || config.maxPoolSize === 0) { - return { poolSize: 0, available: 0, inUse: 0, created: 0 }; - } - - const available = Array.from(pool).filter((agentId) => !this.agents.get(agentId)?.inUse).length; - - const stats = { - poolSize: config.maxPoolSize, - available, - inUse: pool.size - available, - created: pool.size, - }; - - this.logger.debug("Pool statistics", { type, ...stats }); - return stats; - } -} diff --git a/src/agents/agent-state-logger.ts b/src/agents/agent-state-logger.ts deleted file mode 100644 index 3bff2ca..0000000 --- a/src/agents/agent-state-logger.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { TaskConfig, TaskHistoryEntry } from "src/tasks/task-manager.js"; -import { BaseAuditLog, LogUpdate } from "../base/audit-log.js"; -import { AgentConfig, AgentKind, AvailableTool } from "./agent-registry.js"; -import { AgentId } from "./agent-id.js"; - -export const DEFAULT_NAME = "agent_state"; -export const DEFAULT_PATH = ["logs"] as readonly string[]; - -export enum AgentUpdateTypeEnum { - AVAILABLE_TOOLS = "available_tools", - AGENT_CONFIG = "agent_config", - POOL = "pool", - AGENT = "agent", -} - -export interface AvailableToolsData { - agentKind: AgentKind; - availableTools?: AvailableTool[]; -} - -export interface PoolChangeData { - agentKind: AgentKind; - agentType: string; - available: number; - poolSize: number; - inUse: number; - created: number; -} - -export interface AgentLifecycleData { - event: "onCreate" | "onDestroy" | "onAcquire" | "onRelease"; - agentId: AgentId; - taskConfig?: TaskConfig; - historyEntry?: TaskHistoryEntry; -} - -export type AgentStateData = AgentConfig | AvailableToolsData | PoolChangeData | AgentLifecycleData; - -export interface AgentStateUpdate extends LogUpdate {} - -class AgentStateLogger extends BaseAuditLog { - constructor(logPath?: string) { - super(DEFAULT_PATH, DEFAULT_NAME, logPath); - } - - public logAgentConfigCreate(config: AgentConfig) { - this.logUpdate({ - type: AgentUpdateTypeEnum.AGENT_CONFIG, - data: config, - }); - } - - public logPoolChange(data: PoolChangeData) { - this.logUpdate({ - type: AgentUpdateTypeEnum.POOL, - data, - }); - } - - // public logStatusChange(agentId: string, status: Partial) { - // this.logUpdate({ - // timestamp: new Date().toISOString(), - // type: AgentUpdateTypeEnum.STATUS, - // agentId, - // data: status, - // }); - // } - - // public logAgentDestroyed(agentId: string) { - // this.logUpdate({ - // timestamp: new Date().toISOString(), - // type: AgentUpdateTypeEnum.DESTROYED, - // agentId, - // data: undefined, - // }); - // } - - public logAvailableTools(agentKind: AgentKind, availableTools: AvailableTool[]) { - this.logUpdate({ - type: AgentUpdateTypeEnum.AVAILABLE_TOOLS, - data: { - agentKind, - availableTools, - }, - }); - } - - public logAgentLifeCycle(data: AgentLifecycleData) { - this.logUpdate({ - type: AgentUpdateTypeEnum.AGENT, - data, - }); - } -} - -let instance: AgentStateLogger | null = null; - -export const getAgentStateLogger = () => { - if (!instance) { - instance = new AgentStateLogger(); - } - return instance; -}; diff --git a/src/agents/registry/dto.ts b/src/agents/registry/dto.ts new file mode 100644 index 0000000..a6afaa5 --- /dev/null +++ b/src/agents/registry/dto.ts @@ -0,0 +1,123 @@ +import { z } from "zod"; + +export const AgentKindEnumSchema = z + .enum(["supervisor", "operator"]) + .describe( + "Specifies the role type of an agent in the system. A 'supervisor' has administrative privileges and can oversee multiple operators, while an 'operator' handles day-to-day operational tasks.", + ); +export type AgentKindEnum = z.infer; + +export const AgentKindValueSchema = z + .string() + .refine((value) => Object.values(AgentKindEnumSchema.enum).includes(value as AgentKindEnum), { + message: `Agent kind must be one of: ${Object.values(AgentKindEnumSchema.enum).join(", ")}`, + }) + .describe( + "Specifies the role type of an agent in the system. A 'supervisor' has administrative privileges and can oversee multiple operators, while an 'operator' handles day-to-day operational tasks.", + ); +export type AgentKindValue = z.infer; + +export const AgentTypeValueSchema = z + .string() + .describe("Specifies the type of an agent in the system."); +export type AgentTypeValue = z.infer; + +export const AgentIdValueSchema = z + .string() + .describe( + "Unique agent id composed of '{agentKind}:{agentType}[{instanceNum}]:{version}' e.g: 'supervisor:boss[1]:1' or 'operator:poem_generator[2]:3'", + ); +export type AgentIdValue = z.infer; + +export const AgentConfigIdValueSchema = z + .string() + .describe( + "Unique agent config id composed of '{agentKind}:{agentType}:{version}' e.g: 'supervisor:boss:1' or 'operator:poem_generator:3'", + ); +export type AgentConfigIdValue = z.infer; + +export const AgentNumValueSchema = z.number().describe("Agent instance number."); +export type AgentNumValue = z.infer; + +export const AgentConfigVersionValueSchema = z.number().describe("Agent config version number."); +export type AgentConfigVersionValue = z.infer; + +/** + * Schema for configuring an agent type. + * Defines the basic properties and requirements for creating agents of a specific type. + */ +export const AgentConfigSchema = z.object({ + agentConfigId: AgentConfigIdValueSchema, + agentConfigVersion: AgentConfigVersionValueSchema, + agentKind: AgentKindEnumSchema, + agentType: AgentTypeValueSchema, + instructions: z.string().describe("Provide detailed instructions on how the agent should act."), + description: z + .string() + .describe("Description of the agent's behavior and purpose of his existence."), + tools: z.array(z.string()).describe("List of tool identifiers that this agent type can utilize."), + maxPoolSize: z + .number() + .int() + .min(0) + .default(0) + .describe("Maximum number of agents to maintain in the pool for this type."), + autoPopulatePool: z + .boolean() + .default(false) + .describe("Populates the agent pool for a specific type up to its configured size."), +}); +export type AgentConfig = z.infer; + +/** + * Schema for an individual agent instance. + * Represents a specific instance of an agent with its runtime state. + */ +export const AgentSchema = z.object({ + agentId: AgentIdValueSchema, + agentKind: AgentKindEnumSchema, + agentType: AgentTypeValueSchema, + agentNum: AgentNumValueSchema, + agentConfigVersion: AgentConfigVersionValueSchema, + /** Configuration settings for this agent */ + config: AgentConfigSchema, + /** + * Indicates whether this agent is currently being used + * Used for pool management to track available agents + */ + inUse: z.boolean().default(false), + instance: z.any(), +}); +export type Agent = z.infer; +export type AgentWithInstance = Omit & { + instance: TAgentInstance; +}; + +/** + * Schema for an available tool. + */ +export const AvailableToolSchema = z.object({ + name: z.string(), + description: z.string(), +}); + +export type AvailableTool = z.infer; + +/** + * Schema for pool statistics of an agent type + * Provides information about pool capacity and utilization + */ +export const AgentConfigPoolStatsSchema = z + .object({ + /** Maximum number of agents that can be in the pool */ + poolSize: z.number(), + /** Number of agents currently available in the pool */ + available: z.number(), + /** Number of agents currently in use from the pool */ + active: z.number(), + /** Number of created agents */ + created: z.number(), + }) + .describe("Statistics about an agent config's pool"); + +export type AgentConfigPoolStats = z.infer; diff --git a/src/agents/registry/registry.ts b/src/agents/registry/registry.ts new file mode 100644 index 0000000..f05eed7 --- /dev/null +++ b/src/agents/registry/registry.ts @@ -0,0 +1,910 @@ +import { Logger } from "bee-agent-framework/logger/logger"; +import { clone, isNonNullish } from "remeda"; +import { BaseToolsFactory } from "src/base/tools-factory.js"; +import { updateDeepPartialObject } from "src/utils/objects.js"; +import { + agentConfigIdToValue, + agentIdToString, + agentSomeIdToKindValue, + agentSomeIdToTypeValue, + stringToAgentConfig, +} from "../agent-id.js"; +import { agentStateLogger } from "../state/logger.js"; +import { + Agent, + AgentConfig, + AgentConfigIdValue, + AgentConfigPoolStats, + AgentConfigVersionValue, + AgentIdValue, + AgentKindEnum, + AgentKindEnumSchema, + AgentTypeValue, + AgentWithInstance, +} from "./dto.js"; + +/** + * Callbacks for managing agent lifecycle events. + * These callbacks allow customization of agent behavior at key points in their lifecycle. + */ +export interface AgentLifecycleCallbacks { + /** + * Called when a new agent needs to be created + * @param config - Configuration for the agent + * @param agentId - Unique agent ID + * @param poolStats - Statistics of the agent pool + * @param toolsFactory - Factory to create tools + * @returns Promise resolving to the new agent's id and instance + */ + onCreate: ( + config: AgentConfig, + agentId: AgentIdValue, + toolsFactory: BaseToolsFactory, + ) => Promise<{ agentId: AgentIdValue; instance: TAgentInstance }>; + + /** + * Called when an agent is being destroyed + * @param instance - Instance of the agent being destroyed + */ + onDestroy: (instance: TAgentInstance) => Promise; + + /** + * Optional callback when an agent is acquired from the pool + * Use this to prepare an agent for reuse + * @param agentId - ID of the agent being acquired + */ + onAcquire?: (agentId: AgentIdValue) => Promise; + + /** + * Optional callback when an agent is released back to the pool + * Use this to clean up agent state before returning to pool + * @param agentId - ID of the agent being released + */ + onRelease?: (agentId: AgentIdValue) => Promise; +} + +type AgentRuntime = Agent & { instance: TInstance }; + +export interface AgentInstanceRef { + agentId: AgentIdValue; + instance: TInstance; +} + +export type AgentTypesMap = Map>; + +/** + * Registry for managing agent types, instances, and pools. + * Provides functionality for: + * - Registering and managing agent types + * - Creating and destroying agent instances + * - Managing pools of reusable agents + * - Tracking agent lifecycle and utilization + * + * Usage: + * - Whenever you need an agent first look if there already is an suitable agent type if not let register new one. + * + */ +export class AgentRegistry { + private readonly logger: Logger; + /** Map of registered agent kind and their configurations */ + private agentConfigs: Map>; + /** Map of all agent instances */ + private agents = new Map>(); + /** Map of agent pools by kind and type, containing sets of available agent IDs */ + private agentPools: Map< + AgentKindEnum, + Map][]> + >; + /** Callbacks for agent lifecycle events */ + private lifecycleCallbacks: AgentLifecycleCallbacks; + private onAgentConfigCreated: (agentKind: AgentKindEnum, agentType: AgentTypeValue) => void; + /** Maps of tools factories for use by agents per agent kinds */ + private toolsFactory = new Map(); + private poolsCleanupJobIntervalId: NodeJS.Timeout | null = null; + private poolsCleanupJobExecuting = false; + private poolsToCleanup: string[] = []; + + /** + * Creates a new AgentRegistry instance + * @param callbacks - Callbacks for handling agent lifecycle events and agent type registration + */ + constructor({ + agentLifecycle, + onAgentConfigCreated, + }: { + onAgentConfigCreated: (agentKind: AgentKindEnum, agentType: string) => void; + agentLifecycle: AgentLifecycleCallbacks; + }) { + this.logger = Logger.root.child({ name: "AgentRegistry" }); + this.logger.info("Initializing AgentRegistry"); + this.lifecycleCallbacks = agentLifecycle; + this.onAgentConfigCreated = onAgentConfigCreated; + // Initialize agent pools for all agent kinds + this.agentConfigs = new Map(AgentKindEnumSchema.options.map((kind) => [kind, new Map()])); + this.agentPools = new Map(AgentKindEnumSchema.options.map((kind) => [kind, new Map()])); + } + + /** + * Register tools factory for a specific agent type + * @param tuples + */ + registerToolsFactories(tuples: [AgentKindEnum, BaseToolsFactory][]) { + tuples.map(([agentKind, factory]) => { + this.toolsFactory.set(agentKind, factory); + agentStateLogger().logAvailableTools({ + agentKindId: agentSomeIdToKindValue({ agentKind }), + availableTools: factory.getAvailableTools(), + }); + }); + } + + private getAgentKindPoolMap(agentKind: AgentKindEnum) { + const poolKind = this.agentPools.get(agentKind); + if (!poolKind) { + throw new Error(`There is missing pool for agent agentKind:${agentKind}`); + } + return poolKind; + } + + private getAgentTypeVersionSetsArray(agentKind: AgentKindEnum, agentType: AgentTypeValue) { + const poolKind = this.getAgentKindPoolMap(agentKind); + const pool = poolKind.get(agentType); + if (!pool) { + throw new Error( + `There is missing pool version sets array for agent agentKind:${agentKind} agentType:${agentType}`, + ); + } + return pool; + } + + private getAgentTypeVersionSet( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + agentConfigVersion: number, + ) { + const poolVersionSetsArray = this.getAgentTypeVersionSetsArray(agentKind, agentType); + const poolVersionSet = poolVersionSetsArray.find((it) => it[0] === agentConfigVersion); + if (!poolVersionSet) { + throw new Error( + `There is missing pool version set for agent agentKind:${agentKind} agentType:${agentType} version:${agentConfigVersion}`, + ); + } + return poolVersionSet[1]; + } + + private getAgentConfigMap(agentKind: AgentKindEnum) { + const typesMap = this.agentConfigs.get(agentKind); + if (!typesMap) { + throw new Error(`There is missing types map for agent agentKind:${agentKind}`); + } + return typesMap; + } + + private getAgentConfigTypeMap(agentKind: AgentKindEnum, agentType: string) { + const agentConfigTypeMap = this.getAgentConfigMap(agentKind); + const agentVersions = agentConfigTypeMap.get(agentType); + if (!agentVersions) { + this.logger.error("Agent config type map was not found", { agentKind, agentType }); + throw new Error(`Agent kind '${agentKind}' type '${agentType}' was not found`); + } + return agentVersions; + } + + getToolsFactory(agentKind: AgentKindEnum): BaseToolsFactory { + const factory = this.toolsFactory.get(agentKind); + if (!factory) { + this.logger.error(`There is missing tools factory for the '${agentKind}' agent kind.`, { + agentKind, + }); + throw new Error(`There is missing tools factory for the '${agentKind}' agent kind`); + } + + return factory; + } + + createAgentConfig( + config: Omit, + ): AgentConfig { + const { agentKind, agentType, maxPoolSize } = config; + this.logger.info("Create new agent config", { + agentKind, + agentType, + maxPoolSize, + }); + + const agentTypesMap = this.getAgentConfigMap(agentKind); + if (agentTypesMap.has(agentType)) { + this.logger.error("Agent type already registered", { agentType }); + throw new Error(`Agent type '${agentType}' is already registered`); + } + + const toolsFactory = this.getToolsFactory(config.agentKind); + const availableTools = toolsFactory.getAvailableTools(); + if (config.tools.filter((it) => !!it.length).length) { + const undefinedTools = config.tools.filter( + (tool) => !availableTools.some((at) => at.name === tool), + ); + if (undefinedTools.length) { + this.logger.error(`Tool wasn't found between available tools `, { + availableTools: availableTools.map((at) => at.name), + undefinedTools, + }); + throw new Error( + `Tools [${undefinedTools.join(",")}] weren't found between available tools [${availableTools.map((at) => at.name).join(",")}]`, + ); + } + } else { + config.tools = []; + } + + const agentConfigVersion = 1; + const agentConfigId = agentConfigIdToValue({ + ...config, + agentConfigVersion, + }); + const configVersioned = { ...config, agentConfigId, agentConfigVersion }; + agentTypesMap.set(agentType, [configVersioned]); + agentStateLogger().logAgentConfigCreate({ + agentConfigId, + agentType: agentSomeIdToTypeValue(configVersioned), + config: configVersioned, + }); + + this.initializeAgentPool(agentKind, agentType, agentConfigVersion); + this.onAgentConfigCreated(agentKind, agentType); + + return configVersioned; + } + + updateAgentConfig( + update: Pick & + Partial< + Pick< + AgentConfig, + "tools" | "instructions" | "description" | "maxPoolSize" | "autoPopulatePool" + > + >, + ) { + const { agentKind, agentType } = update; + const config = this.getAgentConfig(update.agentKind, update.agentType); + + if (update.tools) { + // Check tools existence + const toolsFactory = this.getToolsFactory(config.agentKind); + const availableTools = toolsFactory.getAvailableTools(); + const undefinedTools = update.tools.filter( + (tool) => !availableTools.some((at) => at.name === tool), + ); + if (undefinedTools.length) { + this.logger.error(`Tool wasn't found between available tools `, { + availableTools: availableTools.map((at) => at.name), + undefinedTools, + }); + throw new Error( + `Tools [${undefinedTools.join(",")}] weren't found between available tools [${availableTools.map((at) => at.name).join(",")}]`, + ); + } + } + + const newConfigVersion = clone(config); + + const agentConfigVersion = config.agentConfigVersion + 1; + const agentConfigId = agentConfigIdToValue({ + ...config, + agentConfigVersion: agentConfigVersion, + }); + updateDeepPartialObject(newConfigVersion, { + ...update, + agentConfigId, + agentConfigVersion, + }); + const configVersions = this.getAgentConfigTypeMap(agentKind, agentType); + configVersions.push(newConfigVersion); + + this.initializeAgentPool(agentKind, agentType, agentConfigVersion); + this.lookupPoolsToClean(); + + agentStateLogger().logAgentConfigUpdate({ + agentType: agentSomeIdToTypeValue(newConfigVersion), + agentConfigId: newConfigVersion.agentConfigId, + config: newConfigVersion, + }); + + return newConfigVersion; + } + + private initializeAgentPool( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + version: number, + ) { + this.logger.debug("Initializing agent pool", { + agentKind, + agentType, + version, + }); + + const kindPool = this.getAgentKindPoolMap(agentKind); + let typePool = kindPool.get(agentType); + if (!typePool) { + typePool = []; + kindPool.set(agentType, typePool); + } + typePool.push([version, new Set([])]); + + this.populatePool(agentKind, agentType, version).catch((error) => { + this.logger.error("Failed to populate pool", { agentType, error }); + }); + } + + /** + * Populates the agent pool for a specific type up to its configured size + * @param agentKind - The agent kind to populate pool for + * @param agentType - The agent type to populate pool for + * @param version - The agent version to populate pool for + * @private + */ + private async populatePool( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + version: number, + ): Promise { + this.logger.debug("Populating agent pool", { agentKind, agentType, version }); + const config = this.getAgentConfig(agentKind, agentType, version); + + if (config.maxPoolSize <= 0) { + this.logger.trace("Pool population skipped - no pool or size 0", { agentType }); + return; + } + + const pool = this.getAgentTypeVersionSet(agentKind, agentType, version); + if (config.autoPopulatePool) { + // Pre-populate pool + const currentPoolSize = pool.size; + const needed = config.maxPoolSize - currentPoolSize; + + this.logger.debug("Creating agents for pool", { + agentType, + needed, + currentPoolSize, + targetSize: config.maxPoolSize, + }); + + for (let i = 0; i < needed; i++) { + await this.createAgent(agentKind, agentType, version); + } + } + } + + private lookupPoolsToClean() { + this.logger.trace("Looking up pools to cleanup"); + this.poolsToCleanup.splice(0); + // Traverse through all pools and try to destroy all unused agents + Array.from(this.agentPools.entries()).forEach(([agentKind, typeMap]) => { + Array.from(typeMap.entries()).forEach(([agentType, versions]) => { + versions.forEach(([version], index, set) => { + const latestVersion = index + 1 >= set.length; + // Schedule + if (!latestVersion) { + this.poolsToCleanup.push( + agentConfigIdToValue({ agentKind, agentType, agentConfigVersion: version }), + ); + } + }); + }); + }); + + if (this.poolsToCleanup.length) { + this.startPoolsCleanupJob(); + } + } + + private startPoolsCleanupJob() { + this.logger.trace("Start cleanup job"); + if (this.poolsCleanupJobIntervalId != null) { + this.logger.warn(`Pool cleanup job is already running`); + } + this.poolsCleanupJobIntervalId = setInterval(async () => { + if (!this.poolsCleanupJobExecuting) { + this.poolsCleanupJobExecuting = true; + this.executePoolsCleanup().catch((err) => { + this.logger.error("Execute pool cleanup job error", err); + this.stopPoolsCleanupJob(); + }); + } + }, 1000); // Runs every 1s + } + + private async executePoolsCleanup() { + this.logger.trace("Executing pool cleanup"); + const poolsToCleanupClone = clone(this.poolsToCleanup); + + let isCleaned = true; + let index = 0; + for (const agentConfigIdStr of poolsToCleanupClone) { + const agentConfigId = stringToAgentConfig(agentConfigIdStr); + const agentTypeVersionPoolSet = this.getAgentTypeVersionSet( + agentConfigId.agentKind, + agentConfigId.agentType, + agentConfigId.agentConfigVersion, + ); + + let destroyed = 0; + for (const agentId of agentTypeVersionPoolSet.values()) { + const agent = this.getAgent(agentId); + if (!agent.inUse) { + try { + await this.destroyAgent(agent.agentId); + destroyed++; + } catch (err) { + this.logger.error(`Cleanup error for agent '${agent.agentId}'`, err); + } + } else { + this.logger.warn(`Can't cleanup agent '${agent.agentId}' he is in use`); + } + } + if (destroyed < agentTypeVersionPoolSet.size) { + isCleaned = false; + } else { + // Destroy unused agent config + this.destroyAgentConfig( + agentConfigId.agentKind, + agentConfigId.agentType, + agentConfigId.agentConfigVersion, + ); + this.poolsToCleanup.splice(index, 1); + } + index++; + } + + if (isCleaned) { + this.stopPoolsCleanupJob(); + } + } + + private stopPoolsCleanupJob() { + this.logger.debug("Stop cleanup job"); + if (this.poolsCleanupJobIntervalId == null) { + this.logger.warn(`Pool cleanup job is already stopped`); + } else { + clearInterval(this.poolsCleanupJobIntervalId); + this.poolsCleanupJobIntervalId = null; + } + } + + /** + * Returns list of all registered agent configs + * @returns Array of agent type identifiers + */ + getAllAgentConfigs(): AgentConfig[] { + this.logger.trace("Getting registered agent configs"); + return Array.from(this.agentConfigs.entries()) + .map(([, typeMap]) => + Array.from(typeMap.entries()) + .map(([, versions]) => versions.at(-1)) + .filter(isNonNullish), + ) + .flat(); + } + + getAgentConfig( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + agentConfigVersion?: number, + ): AgentConfig { + this.logger.trace("Getting agent type configuration", { + agentKind, + agentType, + agentConfigVersion, + }); + const configVersions = this.getAgentConfigMap(agentKind).get(agentType); + if (!configVersions) { + this.logger.error("Agent config not found", { agentType: agentType }); + throw new Error(`Agent kind '${agentKind}' type '${agentType}' was not found`); + } + if (agentConfigVersion != null) { + const configVersion = configVersions.find((c) => c.agentConfigVersion === agentConfigVersion); + if (!configVersion) { + throw new Error( + `Agent kind '${agentKind}' type '${agentType}' version '${agentConfigVersion}' was not found`, + ); + } + return configVersion; + } + + const lastConfigVersion = configVersions.at(-1); + if (lastConfigVersion == null) { + throw new Error(`Agent kind '${agentKind}' type '${agentType}' last version was not found`); + } + return lastConfigVersion; + } + + private destroyAgentConfig( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + agentConfigVersion: number, + ): AgentConfig { + this.logger.trace("Destroying agent configuration", { + agentKind, + agentType, + version: agentConfigVersion, + }); + const configVersions = this.getAgentConfigMap(agentKind).get(agentType); + if (!configVersions) { + this.logger.error("Agent config versions was not found", { agentType: agentType }); + throw new Error( + `Agent kind '${agentKind}' type '${agentType}' config versions was not found`, + ); + } + + const configVersionAt = configVersions.findIndex( + (c) => c.agentConfigVersion === agentConfigVersion, + ); + if (configVersionAt < 0) { + throw new Error( + `Agent kind '${agentKind}' type '${agentType}' version '${agentConfigVersion}' was not found`, + ); + } + const stats = this.getPoolStatsByVersion(agentKind, agentType, agentConfigVersion); + if (stats.active) { + throw new Error( + `Agent config kind '${agentKind}' type '${agentType}' version '${agentConfigVersion}' can't be destroyed while it is still in use.`, + ); + } + + const destroyedConfig = configVersions.splice(configVersionAt, 1)[0]; + const { agentConfigId } = destroyedConfig; + this.logger.info("Agent config destroyed successfully", { + agentConfigId, + agentKind, + agentType, + version: agentConfigVersion, + }); + + if (!configVersions.length) { + this.getAgentConfigMap(agentKind).delete(agentType); + } + + if (!this.getAgentConfigMap(agentKind).size) { + this.agentConfigs.delete(agentKind); + } + + const agentTypeId = agentSomeIdToTypeValue({ + agentKind, + agentType, + }); + agentStateLogger().logAgentConfigDestroy({ + agentConfigId, + agentType: agentTypeId, + }); + return destroyedConfig; + } + + /** + * Acquires an agent instance from the pool or creates a new one + * @param agentKind - The kind of agent to acquire + * @param agentType - The type of agent to acquire + * @param version - The version of agent to acquire + * @returns Promise resolving to the agent ID + * @throws Error if no agents are available and pool is at capacity + */ + async acquireAgent( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + version?: number, + ): Promise> { + this.logger.debug("Attempting to acquire agent", { agentKind, agentType, version }); + const config = this.getAgentConfig(agentKind, agentType, version); + const pool = this.getAgentTypeVersionSet(agentKind, agentType, config.agentConfigVersion); + + if (!pool || config.maxPoolSize === 0) { + this.logger.debug("No pool available, creating new agent", { agentType: agentType }); + return this._acquireAgent( + await this.createAgent(agentKind, agentType, config.agentConfigVersion), + ); + } + + // Try to get an available agent from the pool + for (const agentId of pool) { + const agent = this.agents.get(agentId); + if (agent && !agent.inUse) { + pool.delete(agentId); + this.logger.debug("Acquired agent from pool", { agentType: agentType, agentId }); + return this._acquireAgent(agent); + } + } + + // No available agents in pool + if (pool.size < config.maxPoolSize) { + this.logger.debug("Pool not at capacity, creating new agent", { + agentKind, + agentType, + version: config.agentConfigVersion, + currentSize: pool.size, + maxSize: config.maxPoolSize, + }); + return this._acquireAgent( + await this.createAgent(agentKind, agentType, config.agentConfigVersion), + ); + } + + this.logger.error("No available agents and pool at capacity", { + agentKind, + agentType, + version: config.agentConfigVersion, + poolSize: config.maxPoolSize, + }); + throw new Error( + `No available agents of kind '${agentKind}' type '${agentType}' version '${config.agentConfigVersion}' in pool and pool is at capacity`, + ); + } + + private async _acquireAgent(agent: Agent) { + const { agentId } = agent; + agent.inUse = true; + + if (this.lifecycleCallbacks.onAcquire) { + this.logger.trace("Executing onAcquire callback", { agentId }); + await this.lifecycleCallbacks.onAcquire(agentId); + } + agentStateLogger().logAgentAcquire({ + agentId: agent.agentId, + }); + + const [poolStats, versions] = this.getPoolStats(agent.agentKind, agent.agentType); + agentStateLogger().logPoolChange({ + agentTypeId: agentSomeIdToTypeValue(agent), + poolStats, + versions, + }); + + return agent as AgentWithInstance; + } + + /** + * Releases an agent back to its pool or destroys it + * @param agentId - ID of the agent to release + * @throws Error if agent is not found + */ + async releaseAgent(agentId: AgentIdValue): Promise { + this.logger.debug("Attempting to release agent", { agentId }); + const agent = this.agents.get(agentId); + if (!agent) { + this.logger.error("Agent not found for release", { agentId }); + throw new Error(`Agent with ID '${agentId}' not found`); + } + + const { agentKind, agentType, agentConfigVersion: version, maxPoolSize } = agent.config; + const pool = this.getAgentTypeVersionSet(agentKind, agentType, version); + + if (!pool || maxPoolSize === 0) { + this.logger.debug("No pool available, destroying agent", { agentId }); + await this.destroyAgent(agentId); + return; + } + + if (this.lifecycleCallbacks.onRelease) { + this.logger.trace("Executing onRelease callback", { agentId }); + await this.lifecycleCallbacks.onRelease(agentId); + } + + // Return to pool + agent.inUse = false; + pool.add(agentId); + this.logger.debug("Agent released back to pool", { agentId, agentType: agent.agentType }); + agentStateLogger().logAgentRelease({ + agentId: agent.agentId, + }); + + const [poolStats, versions] = this.getPoolStats(agent.agentKind, agent.agentType); + agentStateLogger().logPoolChange({ + agentTypeId: agentSomeIdToTypeValue(agent), + poolStats, + versions, + }); + } + + private async createAgent( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + agentConfigVersion: number, + ): Promise> { + this.logger.debug("Creating new agent", { agentKind, agentType }); + const config = this.getAgentConfig(agentKind, agentType, agentConfigVersion); + const versionPoolStats = this.getPoolStatsByVersion(agentKind, agentType, agentConfigVersion); + const toolsFactory = this.getToolsFactory(agentKind); + const agentNum = versionPoolStats.created + 1; + const agentId = agentIdToString({ + agentKind, + agentType, + agentNum: agentNum, + agentConfigVersion: agentConfigVersion, + }); + const { instance } = await this.lifecycleCallbacks.onCreate(config, agentId, toolsFactory); + + const agent = { + agentId, + agentKind, + agentType, + agentNum, + agentConfigVersion: config.agentConfigVersion, + config, + inUse: false, + instance, + } satisfies Agent; + this.agents.set(agentId, agent); + + const pool = this.getAgentTypeVersionSetsArray(agentKind, agentType); + let poolVersionSetArrayItem = pool.find((p) => p[0] === agentConfigVersion); + if (!poolVersionSetArrayItem) { + poolVersionSetArrayItem = [agentConfigVersion, new Set([])]; + pool.push(poolVersionSetArrayItem); + } + poolVersionSetArrayItem[1].add(agentId); + this.logger.trace("Added agent to pool", { agentKind, agentType, agentId }); + + this.logger.info("Agent created successfully", { agentId, agentType, agentKind }); + + agentStateLogger().logAgentCreate({ + agentId: agentId, + agentConfigId: config.agentConfigId, + }); + + const [poolStats, versions] = this.getPoolStats(agentKind, agentType); + agentStateLogger().logPoolChange({ + agentTypeId: agentSomeIdToTypeValue(agent), + poolStats, + versions, + }); + return agent; + } + + private async destroyAgent(agentId: AgentIdValue): Promise { + this.logger.debug("Attempting to destroy agent", { agentId }); + const agent = this.agents.get(agentId); + if (!agent) { + this.logger.error("Agent not found for destruction", { agentId }); + throw new Error(`Agent with ID '${agentId}' not found`); + } + + const { agentKind, agentType, agentConfigVersion } = agent; + + await this.lifecycleCallbacks.onDestroy(agent.instance); + + // Remove from pool if it's in one + const poolSet = this.getAgentTypeVersionSet(agentKind, agentType, agentConfigVersion); + if (poolSet) { + poolSet.delete(agentId); + this.logger.trace("Removed agent from pool", { + agentId, + agentKind, + agentType, + agentConfigVersion, + }); + } else { + throw new Error(`Missing pool`); + } + + if (!poolSet.size) { + // Remove pool version array set item + const poolVersionSetsArray = this.getAgentTypeVersionSetsArray(agentKind, agentType); + const poolVersionSet = poolVersionSetsArray.findIndex((it) => it[0] === agentConfigVersion); + poolVersionSetsArray.splice(poolVersionSet, 1); + } + + this.agents.delete(agentId); + this.logger.info("Agent destroyed successfully", { + agentKind, + agentType, + agentConfigVersion, + }); + + agentStateLogger().logAgentDestroy({ + agentId, + }); + + const [poolStats, versions] = this.getPoolStats(agentKind, agentType); + agentStateLogger().logPoolChange({ + agentTypeId: agentSomeIdToTypeValue(agent), + poolStats, + versions, + }); + } + + /** + * Returns list of all active agent instances + * @returns Array of active agents + */ + getActiveAgents( + agentKind?: AgentKindEnum, + agentType?: AgentTypeValue, + agentConfigVersion?: number, + ): Agent[] { + this.logger.trace("Getting active agents"); + return Array.from(this.agents.values()).filter((a) => { + if (agentKind && agentKind !== a.agentKind) { + return false; + } + if (agentType && agentType !== a.agentType) { + return false; + } + if (agentConfigVersion != null && agentConfigVersion !== a.config.agentConfigVersion) { + return false; + } + return a.inUse; + }); + } + + getAgent(agentId: AgentIdValue): Agent { + this.logger.trace("Getting agent by ID", { agentId }); + const agent = this.agents.get(agentId); + if (!agent) { + this.logger.error("Agent not found", { agentId }); + throw new Error(`Agent with ID '${agentId}' not found`); + } + return agent; + } + + getPoolStats( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + ): [AgentConfigPoolStats, [number, AgentConfigPoolStats][]] { + this.logger.trace("Getting pool statistics", { agentKind, agentType }); + const pool = this.getAgentTypeVersionSetsArray(agentKind, agentType); + + if (!pool) { + return [{ poolSize: 0, available: 0, active: 0, created: 0 }, []]; + } + + const versionedAgents = pool.map( + ([version, set]) => [version, Array.from(set).map(this.getAgent.bind(this))] as const, + ); + const versions = versionedAgents.map(([version, agents]) => { + const available = agents.filter((agent) => !agent.inUse).length; + const config = this.getAgentConfig(agentKind, agentType, version); + const stats = { + poolSize: config.maxPoolSize, + available, + active: agents.length - available, + created: agents.length, + } satisfies AgentConfigPoolStats; + return [version, stats] as [number, AgentConfigPoolStats]; + }); + + const stats = versions.reduce( + (prev, [, curr]) => { + const sum = { + available: curr.available + prev.available, + created: curr.created + prev.created, + active: curr.active + prev.active, + poolSize: curr.poolSize + prev.poolSize, + } satisfies AgentConfigPoolStats; + return sum; + }, + { + poolSize: 0, + available: 0, + active: 0, + created: 0, + } satisfies AgentConfigPoolStats, + ); + + this.logger.debug("Pool statistics", { agentType: agentType, ...stats }); + return [stats, versions]; + } + + private getPoolStatsByVersion( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + version: number, + ) { + // FIXME Unoptimized + const [, versions] = this.getPoolStats(agentKind, agentType); + const found = versions.find(([currVersion]) => currVersion === version); + if (!found) { + return { poolSize: 0, available: 0, active: 0, created: 0 }; + } + const [, versionStats] = found; + return versionStats; + } +} diff --git a/src/agents/state/builder.ts b/src/agents/state/builder.ts new file mode 100644 index 0000000..a41c794 --- /dev/null +++ b/src/agents/state/builder.ts @@ -0,0 +1,409 @@ +import { BaseStateBuilder } from "src/base/state/base-state-builder.js"; +import { agentSomeIdToKindValue, stringToAgentConfig, stringToAgentType } from "../agent-id.js"; +import { + AgentConfig, + AgentConfigIdValue, + AgentConfigPoolStats, + AgentIdValue, + AgentKindEnum, + AgentTypeValue, + AvailableTool, +} from "../registry/dto.js"; +import { + AgentAcquireEvent, + AgentConfigCreateEvent, + AgentConfigDestroyEvent, + AgentConfigUpdateEvent, + AgentCreateEvent, + AgentDestroyEvent, + AgentPoolChangeEvent, + AgentReleaseEvent, + AgentStateDataType, + AgentStateDataTypeSchema, + AvailableToolsEvent, + TaskAssignedEvent, + TaskHistoryEntryEvent, + TaskUnassignedEvent, +} from "./dto.js"; + +// Define update types as const to ensure type safety +export const StateUpdateType = { + AGENT_CONFIG: "agent_config", + AGENT: "agent", + POOL: "pool", + TOOLS: "tools", + ASSIGNMENT: "assignment", + FULL: "full", +} as const; + +// Define the type for the update types +export type StateUpdateType = (typeof StateUpdateType)[keyof typeof StateUpdateType]; + +interface Assignment { + assignmentId: string; + assignmentKind: string; + assignedSince: Date; + data: unknown; + history: unknown[]; +} + +export interface AgentInfo { + agentId: string; + agentConfigId: string; + agentConfigVersion: number; + inUse: boolean; + isDestroyed: boolean; + assignments: Map; +} + +export interface AgentPool { + agentType: AgentTypeValue; + poolStats: AgentConfigPoolStats; + versions: [number, AgentConfigPoolStats][]; +} + +export interface AgentState { + agentConfigs: Map; + agentPools: Map>; + agents: Map; + availableTools: Map; + allAvailableTools: Map; +} + +export class AgentStateBuilder extends BaseStateBuilder< + typeof AgentStateDataTypeSchema, + AgentState +> { + constructor() { + super(AgentStateDataTypeSchema, { + agentConfigs: new Map(), + agentPools: new Map(), + agents: new Map(), + availableTools: new Map(), + allAvailableTools: new Map(), + }); + } + + protected reset(): void { + this.state.agentConfigs.clear(); + this.state.agentPools.clear(); + this.state.agents.clear(); + this.state.availableTools.clear(); + this.state.allAvailableTools.clear(); + } + + protected processStateUpdate(data: AgentStateDataType): void { + switch (data.kind) { + case "agent_config_create": + this.handleAgentConfigCreate(data); + this.emit("state:updated", { + type: StateUpdateType.AGENT_CONFIG, + ids: [data.agentConfigId, data.agentType], + }); + break; + + case "agent_config_update": + this.handleAgentConfigUpdate(data); + this.emit("state:updated", { + type: StateUpdateType.AGENT_CONFIG, + ids: [data.agentConfigId, data.agentType], + }); + break; + + case "agent_config_destroy": + this.handleAgentConfigDestroy(data); + this.emit("state:updated", { + type: StateUpdateType.AGENT_CONFIG, + ids: [data.agentConfigId, data.agentType], + }); + break; + + case "pool_change": + this.handlePoolChange(data); + this.emit("state:updated", { + type: StateUpdateType.POOL, + ids: [data.agentTypeId], + }); + break; + + case "available_tools_register": + this.handleAvailableTools(data); + this.emit("state:updated", { + type: StateUpdateType.TOOLS, + ids: [data.agentKindId], + }); + break; + + case "agent_create": + case "agent_acquire": + case "agent_release": + case "agent_destroy": + this.handleAgentLifecycle(data); + this.emit("state:updated", { + type: StateUpdateType.AGENT, + ids: [data.agentId], + }); + break; + + case "assignment_assign": + this.handleAssignmentAssign(data); + this.emit("state:updated", { + type: StateUpdateType.ASSIGNMENT, + ids: [data.agentId, data.assignmentId], + }); + break; + + case "assignment_unassign": + this.handleAssignmentUnassign(data); + this.emit("state:updated", { + type: StateUpdateType.ASSIGNMENT, + ids: [data.agentId, data.assignmentId], + }); + break; + + case "assignment_history_entry": + this.handleAssignmentHistoryEntry(data); + this.emit("state:updated", { + type: StateUpdateType.ASSIGNMENT, + ids: [data.agentId, data.assignmentId], + }); + break; + } + } + + private handleAgentConfigCreate(data: AgentConfigCreateEvent): void { + const { agentType, config } = data; + + // Store the config + this.state.agentConfigs.set(agentType, [config]); + + // Initialize the pool if needed + let pool = this.state.agentPools.get(config.agentKind); + if (!pool) { + pool = new Map(); + this.state.agentPools.set(config.agentKind, pool); + } + + const poolType: AgentPool = { + agentType, + poolStats: { + available: 0, + created: 0, + active: 0, + poolSize: 0, + }, + versions: [], + }; + + pool.set(config.agentType, poolType); + } + + private handleAgentConfigUpdate(data: AgentConfigUpdateEvent): void { + const { agentType, config } = data; + + // Update existing config + const agentConfigVersions = this.state.agentConfigs.get(agentType); + if (!agentConfigVersions) { + throw new Error(`Config versions not found for agent pool type: ${agentType}`); + } + + agentConfigVersions.push(config); + } + + private handleAgentConfigDestroy(data: AgentConfigDestroyEvent): void { + const { agentType } = data; + const agentConfigId = stringToAgentConfig(data.agentConfigId); + + // Remove the config + if (!this.state.agentConfigs.has(agentType)) { + throw new Error(`Agent pool type: ${agentType} was not found for destruction`); + } + this.state.agentConfigs.delete(agentType); + + // Clean up related pool + const agentKindPool = this.state.agentPools.get(agentConfigId.agentKind); + if (agentKindPool) { + agentKindPool.delete(agentConfigId.agentType); + if (agentKindPool.size === 0) { + this.state.agentPools.delete(agentConfigId.agentKind); + } + } + } + + private handlePoolChange(data: AgentPoolChangeEvent): void { + const agentTypeId = stringToAgentType(data.agentTypeId); + const pool = this.state.agentPools.get(agentSomeIdToKindValue(agentTypeId) as AgentKindEnum); + if (!pool) { + throw new Error(`Missing pool for type: ${data.agentTypeId}`); + } + + const poolType = pool.get(agentTypeId.agentType); + if (!poolType) { + throw new Error(`Missing pool type: ${data.agentTypeId}`); + } + + poolType.poolStats = data.poolStats; + poolType.versions = data.versions; + } + + private handleAvailableTools(data: AvailableToolsEvent): void { + this.state.availableTools.set(data.agentKindId, data.availableTools); + + // Update the consolidated tools map + this.state.allAvailableTools.clear(); + Array.from(this.state.availableTools.values()) + .flat() + .forEach((tool) => { + this.state.allAvailableTools.set(tool.name, tool); + }); + } + + private handleAgentLifecycle( + data: AgentCreateEvent | AgentAcquireEvent | AgentReleaseEvent | AgentDestroyEvent, + ): void { + const { agentId } = data; + + switch (data.kind) { + case "agent_create": { + if (this.state.agents.has(agentId)) { + throw new Error(`Agent ${agentId} already exists`); + } + + const agentConfigId = stringToAgentConfig(data.agentConfigId); + + this.state.agents.set(agentId, { + agentId, + agentConfigId: data.agentConfigId, + agentConfigVersion: agentConfigId.agentConfigVersion, + inUse: false, + isDestroyed: false, + assignments: new Map(), + }); + break; + } + case "agent_destroy": { + const agent = this.state.agents.get(agentId); + if (!agent) { + throw new Error(`Agent ${agentId} doesn't exist for destroy`); + } + agent.isDestroyed = true; + break; + } + + case "agent_acquire": { + const agent = this.state.agents.get(agentId); + if (!agent) { + throw new Error(`Agent ${agentId} doesn't exist for acquire`); + } + agent.inUse = true; + break; + } + + case "agent_release": { + const agent = this.state.agents.get(agentId); + if (!agent) { + throw new Error(`Agent ${agentId} doesn't exist for release`); + } + agent.inUse = false; + break; + } + } + } + + private handleAssignmentAssign(data: TaskAssignedEvent): void { + const { agentId, assignmentId, assignmentKind, assignedSince, assignment } = data; + + const agent = this.state.agents.get(agentId); + if (!agent) { + throw new Error(`Undefined agent ${agentId}`); + } + + if (agent.assignments.has(assignmentId)) { + throw new Error(`Assignment ${assignmentId} already exists for agent ${agentId}`); + } + + agent.assignments.set(assignmentId, { + assignmentId, + assignmentKind, + assignedSince, + data: assignment, + history: [], + }); + } + + private handleAssignmentUnassign(data: TaskUnassignedEvent): void { + const { agentId, assignmentId } = data; + + const agent = this.state.agents.get(agentId); + if (!agent) { + throw new Error(`Undefined agent ${agentId}`); + } + + if (!agent.assignments.has(assignmentId)) { + throw new Error(`Assignment ${assignmentId} not found for agent ${agentId}`); + } + + agent.assignments.delete(assignmentId); + } + + private handleAssignmentHistoryEntry(data: TaskHistoryEntryEvent): void { + const { agentId, assignmentId, entry } = data; + + const agent = this.state.agents.get(agentId); + if (!agent) { + throw new Error(`Undefined agent ${agentId}`); + } + + const assignment = agent.assignments.get(assignmentId); + if (!assignment) { + throw new Error(`Assignment ${assignmentId} not found for agent ${agentId}`); + } + + assignment.history.push(entry); + } + + getAgent(agentId: string): AgentInfo | undefined { + return this.state.agents.get(agentId); + } + + getAllAgents(): AgentInfo[] { + return Array.from(this.state.agents.values()); + } + + getAgentConfig(agentTypeId: string, agentVersion?: number): AgentConfig | undefined { + const versions = this.state.agentConfigs.get(agentTypeId); + if (!versions) { + throw new Error(`Agent config versions not found for '${agentTypeId}'`); + } + if (agentVersion != null) { + return versions.find((v) => v.agentConfigVersion === agentVersion); + } + return versions.at(-1); + } + + private getAgentPoolsMap(agentKindId: AgentKindEnum): Map | undefined { + return this.state.agentPools.get(agentKindId); + } + + getAgentPool(agentKind: AgentKindEnum, agentType: string): AgentPool | undefined { + const map = this.getAgentPoolsMap(agentKind); + return map?.get(agentType); + } + + getAvailableTools(poolId: string): AvailableTool[] { + return this.state.availableTools.get(poolId) || []; + } + + getAllTools(): Map { + return this.state.allAvailableTools; + } + + getAgentAssignment(agentId: string, assignmentId: string): Assignment | undefined { + const agent = this.state.agents.get(agentId); + return agent?.assignments.get(assignmentId); + } + + getAgentAssignments(agentId: string): Map | undefined { + return this.state.agents.get(agentId)?.assignments; + } +} diff --git a/src/agents/state/dto.ts b/src/agents/state/dto.ts new file mode 100644 index 0000000..9ecdfa0 --- /dev/null +++ b/src/agents/state/dto.ts @@ -0,0 +1,170 @@ +import { TaskRunHistoryEntrySchema, TaskRunSchema } from "src/tasks/manager/dto.js"; +import { z } from "zod"; +import { + AgentConfigSchema, + AvailableToolSchema, + AgentConfigPoolStatsSchema, + AgentConfigIdValueSchema, + AgentTypeValueSchema, +} from "../registry/dto.js"; +import { DateStringSchema } from "src/base/dto.js"; + +// Base schemas +export const AgentEventKindEnum = z.enum([ + "available_tools_register", + "agent_config_create", + "agent_config_update", + "agent_config_destroy", + "pool_change", + "agent_create", + "agent_acquire", + "agent_release", + "agent_destroy", + "assignment_assign", + "assignment_unassign", + "assignment_history_entry", +]); + +export const BaseAgentEventSchema = z.object({ + kind: AgentEventKindEnum, +}); + +// Agent Config Events +export const BaseAgentConfigLifecycleEventSchema = BaseAgentEventSchema.extend({ + agentConfigId: AgentConfigIdValueSchema, + agentType: AgentTypeValueSchema, +}); +export type AgentConfigLifecycleEvent = z.infer; + +export const AgentConfigCreateEventSchema = BaseAgentConfigLifecycleEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.agent_config_create), + config: AgentConfigSchema, +}); +export type AgentConfigCreateEvent = z.infer; + +export const AgentConfigUpdateEventSchema = BaseAgentConfigLifecycleEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.agent_config_update), + config: AgentConfigSchema, +}); +export type AgentConfigUpdateEvent = z.infer; + +export const AgentConfigDestroyEventSchema = BaseAgentConfigLifecycleEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.agent_config_destroy), +}); +export type AgentConfigDestroyEvent = z.infer; + +// Agent Lifecycle Events +export const BaseAgentLifecycleEventSchema = BaseAgentEventSchema.extend({ + agentId: z.string(), +}); +export type AgentLifecycleEvent = z.infer; + +export const AgentCreateEventSchema = BaseAgentLifecycleEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.agent_create), + agentConfigId: z.string(), +}); +export type AgentCreateEvent = z.infer; + +export const AgentAcquireEventSchema = BaseAgentLifecycleEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.agent_acquire), +}); +export type AgentAcquireEvent = z.infer; + +export const AgentReleaseEventSchema = BaseAgentLifecycleEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.agent_release), +}); +export type AgentReleaseEvent = z.infer; + +export const AgentDestroyEventSchema = BaseAgentLifecycleEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.agent_destroy), +}); +export type AgentDestroyEvent = z.infer; + +// Assignment Events +export const AssignmentKindEnum = z.enum(["task"]); + +export const BaseAssignmentEventSchema = BaseAgentEventSchema.extend({ + agentId: z.string(), + assignmentId: z.string(), + assignmentKind: AssignmentKindEnum, +}); +export type BaseAssignmentEvent = z.infer; + +export const AssignedEventSchema = (assignmentSchema: TAssignment) => + BaseAssignmentEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.assignment_assign), + assignedSince: DateStringSchema, + assignment: assignmentSchema, + }); +export type AssignedEvent = z.infer< + ReturnType> +>; + +export const UnassignedEventSchema = BaseAssignmentEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.assignment_unassign), + unassignedAt: DateStringSchema, +}); +export type UnassignedEvent = z.infer; + +export const HistoryEntryCreateEventSchema = ( + historyEntrySchema: THistoryEntry, +) => + BaseAssignmentEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.assignment_history_entry), + entry: historyEntrySchema, + }); +export type HistoryEntryCreateEvent = z.infer< + ReturnType> +>; + +// Task-specific Assignment Events +export const TaskAssignedEventSchema = AssignedEventSchema(TaskRunSchema).extend({ + assignmentKind: z.literal(AssignmentKindEnum.enum.task), +}); +export type TaskAssignedEvent = z.infer; + +export const TaskUnassignedEventSchema = UnassignedEventSchema.extend({ + assignmentKind: z.literal(AssignmentKindEnum.enum.task), +}); +export type TaskUnassignedEvent = z.infer; + +export const TaskHistoryEntryEventSchema = HistoryEntryCreateEventSchema( + TaskRunHistoryEntrySchema, +).extend({ + assignmentKind: z.literal(AssignmentKindEnum.enum.task), +}); +export type TaskHistoryEntryEvent = z.infer; + +// Available Tools Events +export const AvailableToolsEventSchema = BaseAgentEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.available_tools_register), + agentKindId: z.string(), + availableTools: z.array(AvailableToolSchema), +}); +export type AvailableToolsEvent = z.infer; + +// Pool Events +export const AgentPoolChangeEventSchema = BaseAgentEventSchema.extend({ + kind: z.literal(AgentEventKindEnum.enum.pool_change), + agentTypeId: z.string(), + poolStats: AgentConfigPoolStatsSchema, + versions: z.array(z.tuple([z.number(), AgentConfigPoolStatsSchema])), +}); +export type AgentPoolChangeEvent = z.infer; + +// Union of all event types +export const AgentStateDataTypeSchema = z.discriminatedUnion("kind", [ + AvailableToolsEventSchema, + AgentConfigCreateEventSchema, + AgentConfigUpdateEventSchema, + AgentConfigDestroyEventSchema, + AgentCreateEventSchema, + AgentAcquireEventSchema, + AgentReleaseEventSchema, + AgentDestroyEventSchema, + AgentPoolChangeEventSchema, + TaskAssignedEventSchema, + TaskUnassignedEventSchema, + TaskHistoryEntryEventSchema, +]); +export type AgentStateDataType = z.infer; diff --git a/src/agents/state/logger.ts b/src/agents/state/logger.ts new file mode 100644 index 0000000..2ba9912 --- /dev/null +++ b/src/agents/state/logger.ts @@ -0,0 +1,147 @@ +import { BaseStateLogger } from "../../base/state/base-state-logger.js"; +import { + AgentAcquireEvent, + AgentConfigCreateEvent, + AgentConfigDestroyEvent, + AgentConfigUpdateEvent, + AgentCreateEvent, + AgentDestroyEvent, + AgentEventKindEnum, + AgentPoolChangeEvent, + AgentReleaseEvent, + AgentStateDataTypeSchema, + AssignmentKindEnum, + AvailableToolsEvent, + TaskAssignedEvent, + TaskHistoryEntryEvent, + TaskUnassignedEvent, +} from "./dto.js"; + +export const DEFAULT_NAME = "agent_state"; +export const DEFAULT_PATH = ["logs"] as readonly string[]; + +class AgentStateLogger extends BaseStateLogger { + constructor(logPath?: string) { + super(DEFAULT_PATH, DEFAULT_NAME, logPath); + } + + public logAvailableTools(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.enum.available_tools_register, + ...data, + }, + }); + } + + public logAgentConfigCreate(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.Values.agent_config_create, + ...data, + }, + }); + } + + public logAgentConfigUpdate(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.Values.agent_config_update, + ...data, + }, + }); + } + + public logAgentConfigDestroy(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.Values.agent_config_destroy, + ...data, + }, + }); + } + + public logAgentCreate(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.enum.agent_create, + ...data, + }, + }); + } + + public logAgentAcquire(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.enum.agent_acquire, + ...data, + }, + }); + } + + public logAgentRelease(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.enum.agent_release, + ...data, + }, + }); + } + + public logAgentDestroy(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.enum.agent_destroy, + ...data, + }, + }); + } + + public logPoolChange(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.Values.pool_change, + ...data, + }, + }); + } + + public logTaskAssigned(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.Values.assignment_assign, + assignmentKind: AssignmentKindEnum.enum.task, + ...data, + }, + }); + } + + public logTaskUnassigned(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.enum.assignment_unassign, + assignmentKind: AssignmentKindEnum.enum.task, + ...data, + }, + }); + } + + public logTaskHistoryEntry(data: Omit) { + this.logUpdate({ + data: { + kind: AgentEventKindEnum.enum.assignment_history_entry, + assignmentKind: AssignmentKindEnum.Values.task, + ...data, + }, + }); + } +} + +let instance: AgentStateLogger | null = null; + +export const agentStateLogger = () => { + if (!instance) { + instance = new AgentStateLogger(); + } + return instance; +}; diff --git a/src/agents/supervisor.ts b/src/agents/supervisor.ts index fb56250..c3ab32d 100644 --- a/src/agents/supervisor.ts +++ b/src/agents/supervisor.ts @@ -1,67 +1,56 @@ import { BaseToolsFactory, ToolFactoryMethod } from "src/base/tools-factory.js"; -import { TaskManager } from "src/tasks/task-manager.js"; +import { TaskManager } from "src/tasks/manager/manager.js"; import { TaskManagerTool, TOOL_NAME as taskManagerToolName } from "src/tasks/tool.js"; -import { AgentRegistry } from "./agent-registry.js"; +import { AgentRegistry } from "./registry/registry.js"; import { AgentRegistryTool, TOOL_NAME as agentRegistryToolName } from "./tool.js"; export enum AgentTypes { BOSS = "boss", } +// (agentKind: ${agentKind}, agentType: ${agentType}, agentId: ${agentId}) + export const SUPERVISOR_INSTRUCTIONS = ( agentKind: string, agentType: string, agentId: string, -) => `You are an AI Coordinator (agentKind: ${agentKind}, agentType: ${agentType}, agentId: ${agentId}) responsible for managing agent deployment and task execution across the system. You work with two primary systems: -- ${agentRegistryToolName}: Manages the pool of AI agents -- ${taskManagerToolName}: Handles task creation and execution - -The system automatically assigns agents from ${agentRegistryToolName} to tasks in ${taskManagerToolName} based on agent type and kind requirements. - -## Core Responsibilities - -1. Agent Type Management - - Create appropriate agent types before task execution - - Configure each agent type with: - - Essential tools only (verified for availability) - - Each agent has llm capabilities so they don't need an extra tool for tasks like generation, summarization etc. - - Detailed step-by-step instructions - - Clear output format requirements - - Specific tool usage guidelines - - Set optimal pool size based on expected task parallelization needs - -2. Task Management - - Explicitly start tasks using appropriate functions - - Scheduling doesn't automatically run the task. - - Ensure each task has a clearly defined topic and purpose - - Monitor task status and execution details - - Verify task completion and agent release back to the pool - -## System Workflow -1. Agent Lifecycle: - - Agents are automatically assigned from pool when tasks start - - Agents return to pool upon task completion or failure - -2. Task Lifecycle: - - Task creation requires valid agent type - - Task execution triggers automatic agent assignment - - Task completion triggers automatic agent release - -## Best Practices -1. Pre-execution Verification - - Confirm entity existence before task/agent operations - - Validate tool availability for agent types - - Verify pool capacity for parallel execution - -2. Resource Optimization - - Maintain appropriate pool sizes - - Monitor agent utilization - - Ensure efficient task distribution - -## Notes -- Never use your capabilities on tasks intended to another agent. +) => `You are a supervisor AI assistant (ID:${agentId}) who manages a multi-agent platform that consisted of two main systems: agent registry and task manager. +* **Agent registry** (tool:${agentRegistryToolName}) + * Serves to manage agents. + * Agent means in this context an umbrella name for agent configuration aka agent config and their instances aka agents. + * Agent config is a general definition of particular sort of agent instructed to solve a particular sort of tasks like an agent 'poem_generator' configured to generate poem on some topic (passed by task input). + * Agent config is a template for agent instances. Agent is an actual instance of an agent config. + * Agent configs are divided into two main groups by agent kind: + * **supervisor** + * Agents like you who are able to manage multi-agent platform. + * **operator** + * Agents who serves to complete tasks. + * Each agent config has own unique agent type that corresponds to their purpose like 'poem_generator' who is an agent dedicated to generate poems task. + * Each agent has an unique ID composed of '{agentKind}:{agentType}[{instanceNum}]:{version}' like 'supervisor:boss[1]:1' or 'operator:poem_generator[2]:3'. + * ** Agent pool ** + * Each agent configuration automatically creates a pool of agent instances based on parameters of the pool setting. + * These agent instances are then available to be assigned to a related task. + * Each agent instance can work on exactly one task at time. If there is no enough instances to work on scheduled tasks you can extend the pool size or on the other hand, if there is many unused agent instance for long time, to shrink it. + * ** Remember ** + * Before creating a new agent config, you should check whether an existing agent config with the same functionality already exists (use function to list all configs). If it does, use it instead of creating a new one. However, be cautious when updating it—its purpose should not change, as there may be dependencies that rely on its original function. +* **Task manager** (tool:${taskManagerToolName}). + * Serves to manage tasks. + * Task means in this context an umbrella name for task configuration aka task config and their instances aka task runs. + * Task config is a general definition of particular sort of task that should solve some particular sort of problems like a task to generate poem on some topic like 'poem_generation' instead of task config to generate poem on specific topic like 'poem_love_generation'. + * Task config is a template for a task run. Task run is an actual instance of a task config. + * Each task config has own unique task type that corresponds to their purpose like 'poem_generation' which is a task dedicated to generate poems on some topic. The topic will be provided to the task run during instantiation like input:'black cat' to the task run instantiated from 'poem_generation' task config. + * Each task config is always assigned to the one specific agent config without version specification in this format '{agentKind}:{agentType}' like 'operator:poem_generator' that means when the task will run it will be assigned to the agent of 'operator' kind and 'poem_generator' type in the latest version. + * Each task run has an unique ID composed of 'task:{taskType}[{instanceNum}]:{version}' like 'task:poem_generation[1]:1' or 'task:text_summarization[2]:3'. + * Exclusive concurrency mode should be used only for tasks that should not run simultaneously, such as when multiple instances would compete for the same database resources, potentially causing deadlocks and slowing down the overall process. + * ** Task pool ** + * Task pool is different from agent pool because it doesn't auto-instantiate tasks it would be pointless because tasks need a specific input when they are instantiated and we don't know him ahead. + * Task pool also doesn't have pool size limit because tasks existence is not resource-intensive they can stay there until some agent is ready to execute it. + * ** Remember ** + * Before creating a new task config, you should check whether an existing task config with the same functionality already exists (use function to list all configs). If it does, use it instead of creating a new one. However, be cautious when updating it—its purpose should not change, as there may be dependencies that rely on its original function. +* **Task-agent relation** + * Task configs are assigned to the agent configs which secures that if task run is created it is put to the task pool and the platform will care about its assignment to the specific instance of the relevant agent. If the pool of relevant agent has an available agent it auto-assign him to the task run if not the task run will be wait until some will be available. -Your primary goal is to maximize system efficiency through proper agent coordination and resource management while maintaining system integrity and control.`; +Your primary mission is to assist the user in achieving their goals, whether through direct conversation or by orchestrating tasks within the system. You must recognize when a task should be created and when it is unnecessary, ensuring that existing tasks and agents are utilized efficiently before initiating new ones. Task execution drives the platform—before creating a task, verify that a similar one does not already exist, and before creating an agent, ensure there is a task that necessitates it. Your role is to plan, coordinate, and optimize task execution, ensuring a seamless and intelligent workflow.`; export class ToolsFactory extends BaseToolsFactory { constructor( diff --git a/src/agents/tool.ts b/src/agents/tool.ts index 1b95389..719ec9d 100644 --- a/src/agents/tool.ts +++ b/src/agents/tool.ts @@ -7,19 +7,207 @@ import { ToolInput, } from "bee-agent-framework/tools/base"; import { z } from "zod"; +import { AgentInstanceRef, AgentRegistry } from "./registry/registry.js"; import { Agent, AgentConfig, + AgentConfigPoolStats, AgentConfigSchema, - AgentInstanceRef, - AgentKindSchema, - AgentRegistry, + AgentKindEnumSchema, AvailableTool, - PoolStats, -} from "./agent-registry.js"; +} from "./registry/dto.js"; export const TOOL_NAME = "agent_registry"; +// Comprehensive manual to use the tool. Source of truth for TOOL_RULES +export const TOOL_MANUAL = `# Agent Registry System Manual + +## Prerequisites + +To work with Agent Registry, ensure that: +* Tools Factory is registered for your agent kind +* Required agent configurations are defined +* You understand the agent lifecycle phases +* You have necessary permissions for agent management + +## Working Process + +### Creating Agent Configuration + +Agent configuration requires: +* **Agent Kind** + * Must be either supervisor or operator + * Defines the role and permissions level + +* **Agent Type** + * Must be unique identifier + * Describes specific function or purpose + +* **Instructions** + * Detailed behavior step-by-step guidelines + * Specific action protocols + * Performance expectations + * Expected output format + +* **Description** + * Purpose of existence + * Main responsibilities + * Expected outcomes + +* **Tools Access** + * List of specific tools + * Empty array for no tools + +* **Pool Settings** + * Maximum pool size + * Auto population preferences + +### Working with Tools + +Before agent creation: +* Get available tools for the agent kind and decide which should be used. + +### Understanding Pool System + +Pool management follows these principles: +* Each agent type has dedicated pool +* Pool size is fixed by configuration +* System handles all lifecycle events +* Automatic agent acquisition/release + +### Agent Lifecycle Phases + +Automatic system management includes: +* Creation of new agents when needed +* Pool management and optimization +* Task-based agent acquisition +* Post-task agent release +* Cleanup of unused agents + +### System Monitoring + +Available monitoring capabilities: +* Active agent tracking +* Pool utilization statistics +* Configuration version control +* Tool usage analytics + +## Key Considerations + +### Pool Management + +For optimal pool operation: +* Size pools based on workload +* Consider enabling auto-population +* Monitor utilization patterns +* Review resource allocation + +### Version Control + +Configuration versioning rules: +* All configurations are versioned +* New versions can be added +* Old versions remain accessible +* Version changes are tracked + +### Resource Optimization + +For best performance: +* Align pool sizes with needs +* Track resource consumption +* Monitor agent metrics +* Optimize tool distribution + +## System Limitations + +### Pool Constraints + +Fixed limitations include: +* Maximum size per agent type +* Automatic lifecycle only +* No manual agent management +* Fixed pool boundaries + +### Configuration Rules + +Remember that: +* Tools require pre-registration +* Agent types are predefined +* Configurations are immutable +* Changes require new versions + +## Best Practices + +### Configuration Setup + +When creating configurations: +* Write clear instructions +* Set realistic pool sizes +* Consider auto-population +* Define tool requirements + +### Resource Management + +For effective operation: +* Check pool utilization +* Monitor active agents +* Watch resource usage +* Review performance metrics + +### Error Prevention + +To minimize issues: +* Verify tool availability +* Validate configurations +* Monitor pool capacity +* Track error patterns + +## Important Rules + +Remember these key points: +* Configurations must exist before agents +* System manages all lifecycle events +* Monitor pools for health/utilization +* Track tools and configurations +* Regular performance review needed`; + +// Distilled manual for system prompt +export const TOOL_RULES = `# Agent Registry Rules + +You are working with an Agent Registry system. Follow these rules: + +1. Before requesting any agent operations, ensure a valid agent configuration exists that specifies: + * Agent Kind (supervisor/operator) + * Agent Type (unique identifier) + * Instructions for behavior + * Tools access settings + * Pool configuration + +2. Understand that agents are managed automatically: + * You cannot create or destroy agents directly + * Agents are acquired automatically when tasks start + * Agents are released automatically when tasks complete + * Pool sizes are fixed by configuration + +3. When working with tools: + * Only use tools registered for your agent kind + * Verify tool availability before tasks + * Null means all tools available + * Empty array means no tools allowed + * Agents doesn't need tools for llm capabilities + +4. For monitoring: + * Track active agents in your tasks + * Check pool statistics when needed + * Monitor tool availability + * Report any issues encountered + +5. Remember: + * Always check configurations before tasks + * Let the system handle agent lifecycle + * Work within pool size limits + * Use only available tools`; + export interface AgentRegistryToolInput extends BaseToolOptions { registry: AgentRegistry; } @@ -30,8 +218,9 @@ export type AgentRegistryToolResultData = | AgentConfig | string | Agent[] + | AgentConfig[] | Agent - | PoolStats + | [AgentConfigPoolStats, [number, AgentConfigPoolStats][]] | AgentInstanceRef | AvailableTool[]; @@ -44,44 +233,66 @@ export interface AgentRegistryToolResult { export const GetAvailableToolsSchema = z .object({ method: z.literal("getAvailableTools"), - agentKind: AgentKindSchema.describe("Kind of agent is mandatory."), + agentKind: AgentKindEnumSchema.describe("Kind of agent is mandatory."), }) - .describe("Get all available tools usable in agents"); + .describe( + "Get all available tools usable in agents. Use this always before try to assign any tool.", + ); -export const RegisterAgentTypeSchema = z +export const CreateAgentConfigSchema = z .object({ - method: z.literal("registerAgentType"), - agentKind: z.literal(AgentKindSchema.Enum.operator), + method: z.literal("createAgentConfig"), + agentKind: z.literal(AgentKindEnumSchema.Enum.operator), config: AgentConfigSchema.omit({ agentKind: true, }), }) - .describe("Register a new agent type with its configuration."); + .describe("Create a new agent configuration."); -export const GetAgentTypesSchema = z +export const UpdateAgentConfigSchema = z .object({ - method: z.literal("getAgentTypes"), + method: z.literal("updateAgentConfig"), + agentKind: z.literal(AgentKindEnumSchema.Enum.operator), + agentType: z.string(), + config: AgentConfigSchema.partial().pick({ + instructions: true, + description: true, + tools: true, + autoPopulatePool: true, + maxPoolSize: true, + }), }) - .describe("Get all registered agent types"); + .describe("Update an existing agent configuration."); -export const GetAgentTypeConfigSchema = z +export const GetAllAgentConfigsSchema = z .object({ - method: z.literal("getAgentTypeConfig"), - agentKind: AgentKindSchema, - type: z.string(), + method: z.literal("getAllAgentConfigs"), }) - .describe("Get configuration for a specific agent type"); + .describe("Get all registered agent configs"); -export const DestroyAgentSchema = z +export const GetAgentConfigSchema = z .object({ - method: z.literal("destroyAgent"), - agentId: z.string(), + method: z.literal("getAgentConfig"), + agentKind: AgentKindEnumSchema, + agentType: z.string(), + }) + .describe("Get latest agent configuration for a specific agent kind and type"); + +export const GetAgentConfigVersionSchema = z + .object({ + method: z.literal("getAgentConfigVersion"), + agentKind: AgentKindEnumSchema, + agentType: z.string(), + version: z.number().optional().describe(`Not specified means last version`), }) - .describe("Destroy an existing agent instance"); + .describe("Get specific version of agent configuration"); export const GetActiveAgentsSchema = z .object({ method: z.literal("getActiveAgents"), + agentKind: AgentKindEnumSchema, + agentType: z.string(), + agentConfigVersion: z.number().optional().describe(`Not specified means any version`), }) .describe("Get all active agent instances"); @@ -95,10 +306,12 @@ export const GetAgentSchema = z export const GetPoolStatsSchema = z .object({ method: z.literal("getPoolStats"), - agentKind: AgentKindSchema, - type: z.string(), + agentKind: AgentKindEnumSchema, + agentType: z.string(), }) - .describe("Get statistics about the agent pool for a specific type"); + .describe( + "Get statistics about the agent's pool for a specific agent configuration kind and type", + ); /** * Tool for interacting with the AgentRegistry @@ -132,10 +345,11 @@ export class AgentRegistryTool extends Tool< inputSchema() { return z.discriminatedUnion("method", [ GetAvailableToolsSchema, - RegisterAgentTypeSchema, - GetAgentTypesSchema, - GetAgentTypeConfigSchema, - DestroyAgentSchema, + CreateAgentConfigSchema, + UpdateAgentConfigSchema, + GetAllAgentConfigsSchema, + GetAgentConfigSchema, + GetAgentConfigVersionSchema, GetActiveAgentsSchema, GetAgentSchema, GetPoolStatsSchema, @@ -148,20 +362,31 @@ export class AgentRegistryTool extends Tool< case "getAvailableTools": data = this.registry.getToolsFactory(input.agentKind).getAvailableTools(); break; - case "registerAgentType": - data = this.registry.registerAgentType({ ...input.config, agentKind: input.agentKind }); + case "createAgentConfig": + data = this.registry.createAgentConfig({ ...input.config, agentKind: input.agentKind }); + break; + case "updateAgentConfig": + data = this.registry.updateAgentConfig({ + ...input.config, + agentKind: input.agentKind, + agentType: input.agentType, + }); break; - case "getAgentTypes": - data = this.registry.getAgentTypes(); + case "getAllAgentConfigs": + data = this.registry.getAllAgentConfigs(); break; - case "getAgentTypeConfig": - data = this.registry.getAgentTypeConfig(input.agentKind, input.type); + case "getAgentConfig": + data = this.registry.getAgentConfig(input.agentKind, input.agentType); break; - case "destroyAgent": - data = await this.registry.destroyAgent(input.agentId); + case "getAgentConfigVersion": + data = this.registry.getAgentConfig(input.agentKind, input.agentType, input.version); break; case "getActiveAgents": - data = this.registry.getActiveAgents(); + data = this.registry.getActiveAgents( + input.agentKind, + input.agentType, + input.agentConfigVersion, + ); data = data.map((it) => ({ ...it, instance: undefined })); break; case "getAgent": @@ -169,7 +394,7 @@ export class AgentRegistryTool extends Tool< data = { ...data, instance: undefined }; break; case "getPoolStats": - data = this.registry.getPoolStats(input.agentKind, input.type); + data = this.registry.getPoolStats(input.agentKind, input.agentType); break; } return new JSONToolOutput({ diff --git a/src/base/dto.ts b/src/base/dto.ts new file mode 100644 index 0000000..3526573 --- /dev/null +++ b/src/base/dto.ts @@ -0,0 +1,3 @@ +import { z } from "zod"; + +export const DateStringSchema = z.union([z.string().transform((str) => new Date(str)), z.date()]); diff --git a/src/base/entity-id.ts b/src/base/entity-id.ts new file mode 100644 index 0000000..8e6d32b --- /dev/null +++ b/src/base/entity-id.ts @@ -0,0 +1,198 @@ +// Base entity ID interfaces +export interface EntityKindId { + kind: TKind; +} + +export interface EntityTypeId extends EntityKindId { + type: string; +} + +// Version can exist without a number +export interface EntityVersionId extends EntityTypeId { + version: number; +} + +// Number is optional and can be combined with version +export interface EntityNumId extends EntityTypeId { + num: number; +} + +export interface EntityVersionNumId extends EntityVersionId { + num: number; +} + +// Type guards +export function hasType(entity: EntityKindId): entity is EntityTypeId { + return "type" in entity; +} + +export function hasNum( + entity: EntityKindId, +): entity is EntityNumId | EntityVersionNumId { + return "num" in entity; +} + +export function hasVersion( + entity: EntityKindId, +): entity is EntityVersionId | EntityVersionNumId { + return "version" in entity; +} + +// Base string conversion functions +function kindString(entity: EntityKindId): string { + return `${entity.kind}`; +} + +function typeString(entity: EntityTypeId): string { + return `${entity.type}`; +} + +function versionString(version: number): string { + return `${version}`; +} + +function numString(num: number): string { + return `[${num}]`; +} + +// Specific entity to string conversions +export function entityToKindString(entity: EntityKindId): string { + return kindString(entity); +} + +export function entityToTypeIdString(entity: EntityTypeId): string { + return `${kindString(entity)}:${typeString(entity)}`; +} + +export function entityToVersionIdString(entity: EntityVersionId): string { + return `${entityToTypeIdString(entity)}:${versionString(entity.version)}`; +} + +export function entityToNumIdString(entity: EntityNumId): string { + return `${entityToTypeIdString(entity)}${numString(entity.num)}`; +} + +export function entityToVersionNumIdString(entity: EntityVersionNumId): string { + return `${entityToTypeIdString(entity)}${numString(entity.num)}:${versionString(entity.version)}`; +} + +// Generic toString that handles any entity type and outputs full string +export function entityToString( + entity: + | EntityKindId + | EntityTypeId + | EntityVersionId + | EntityNumId + | EntityVersionNumId, +): string { + if (hasVersion(entity) && hasNum(entity)) { + return entityToVersionNumIdString(entity as EntityVersionNumId); + } + if (hasVersion(entity)) { + return entityToVersionIdString(entity as EntityVersionId); + } + if (hasNum(entity)) { + return entityToNumIdString(entity as EntityNumId); + } + if (hasType(entity)) { + return entityToTypeIdString(entity); + } + return entityToKindString(entity); +} + +// Generic parsing functions +export function stringToEntityKind( + str: string, + kindValidator: (kind: string) => TKind, +): EntityKindId { + return { + kind: kindValidator(str), + }; +} + +export function stringToEntityType( + str: string, + kindValidator: (kind: string) => TKind, +): EntityTypeId { + const [kind, type] = str.split(":"); + if (!type) { + throw new Error(`Invalid entity type ID format: ${str}`); + } + + return { + kind: kindValidator(kind), + type, + }; +} + +export function stringToEntityVersion( + str: string, + kindValidator: (kind: string) => TKind, +): EntityVersionId { + let kindPart: string; + let typePart: string; + let version: string; + const kindTypeParts = []; + const splitted = str.split(":"); + if (splitted.length > 2) { + [kindPart, typePart, version] = splitted; + kindTypeParts.push(kindPart, typePart); + } else { + [typePart, version] = splitted; + kindTypeParts.push(typePart); + } + + if (!version) { + throw new Error(`Invalid entity version ID format: ${str}`); + } + + const typeId = stringToEntityType(kindTypeParts.join(":"), kindValidator); + return { + ...typeId, + version: parseInt(version, 10), + }; +} + +export function stringToEntityNum( + str: string, + kindValidator: (kind: string) => TKind, +): EntityNumId { + const match = str.match(/^(.*?)\[(\d+)\]$/); + if (!match) { + throw new Error(`Invalid entity num ID format: ${str}`); + } + + const typeId = stringToEntityType(match[1], kindValidator); + return { + ...typeId, + num: parseInt(match[2], 10), + }; +} + +export function stringToEntityVersionNum( + str: string, + kindValidator: (kind: string) => TKind, +): EntityVersionNumId { + let kindPart: string; + let typePart: string; + let version: string; + const kindTypeParts = []; + const splitted = str.split(":"); + if (splitted.length > 2) { + [kindPart, typePart, version] = splitted; + kindTypeParts.push(kindPart, typePart); + } else { + [typePart, version] = splitted; + kindTypeParts.push(typePart); + } + + if (!version) { + throw new Error(`Invalid entity version num ID format: ${str}`); + } + + const numId = stringToEntityNum(kindTypeParts.join(":"), kindValidator); + return { + ...numId, + version: parseInt(version, 10), + }; +} diff --git a/src/base/state/base-state-builder.ts b/src/base/state/base-state-builder.ts new file mode 100644 index 0000000..cfd019e --- /dev/null +++ b/src/base/state/base-state-builder.ts @@ -0,0 +1,242 @@ +import { EventEmitter } from "events"; +import { createReadStream, FSWatcher, watch, statSync } from "fs"; +import { createInterface } from "readline"; +import { z } from "zod"; +import { LogInitSchema } from "./dto.js"; + +export interface StateUpdateEvent { + type: string; + ids?: string[]; +} + +interface StateBuilderEvents { + "log:reset": () => void; + "log:new_line": (line: string) => void; + "state:updated": (update: StateUpdateEvent) => void; + "error": (error: Error) => void; +} + +export abstract class BaseStateBuilder< + TSchema extends z.ZodType, + TState extends Record, +> extends EventEmitter { + protected state: TState; + private readonly updateSchema: z.ZodObject<{ + timestamp: z.ZodString; + data: TSchema; + }>; + private watcher: FSWatcher | null = null; + private currentPosition = 0; + private lastKnownSize = 0; + private isProcessing = false; + private processingQueue: (() => Promise)[] = []; + private isWatching = false; + + constructor( + protected readonly dataSchema: TSchema, + initialState: TState, + ) { + super(); + this.state = initialState; + this.updateSchema = z.object({ + timestamp: z.string(), + data: this.dataSchema, + }); + } + + public on(event: K, listener: StateBuilderEvents[K]): this { + return super.on(event, listener); + } + + public emit( + event: K, + ...args: Parameters + ): boolean { + return super.emit(event, ...args); + } + + public getState(): TState { + return this.state; + } + + private async processNextInQueue(): Promise { + if (this.isProcessing || this.processingQueue.length === 0) { + return; + } + + this.isProcessing = true; + try { + const nextProcess = this.processingQueue.shift(); + if (nextProcess) { + await nextProcess(); + } + } finally { + this.isProcessing = false; + if (this.processingQueue.length > 0) { + await this.processNextInQueue(); + } + } + } + + private queueProcess(process: () => Promise): void { + this.processingQueue.push(process); + if (!this.isProcessing) { + this.processNextInQueue().catch((error) => { + this.emit("error", new Error(`Queue processing error: ${error.message}`)); + }); + } + } + + public async watchLogFile(filePath: string): Promise { + if (this.isWatching) { + throw new Error("Already watching a file"); + } + + try { + this.isWatching = true; + + // Get initial file size + const stats = statSync(filePath); + this.lastKnownSize = stats.size; + + // Initial read of the entire file + await this.processEntireFile(filePath); + + // Set up file watcher + this.watcher = watch(filePath, { persistent: true }, async (eventType) => { + if (eventType === "change") { + this.queueProcess(async () => { + const currentStats = statSync(filePath); + const currentSize = currentStats.size; + + // Check if file was truncated + if (currentSize < this.lastKnownSize) { + // File was truncated, reset position and process entire file + this.currentPosition = 0; + await this.processEntireFile(filePath); + } else { + // Normal append case + await this.processFileChanges(filePath); + } + + this.lastKnownSize = currentSize; + }); + } + }); + + this.watcher.on("error", (error) => { + this.emit("error", new Error(`File watch error: ${error.message}`)); + }); + } catch (error) { + this.isWatching = false; + if (error instanceof Error) { + this.emit("error", new Error(`Failed to start file watching: ${error.message}`)); + } + throw error; + } + } + + public stopWatching(): void { + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + } + this.isWatching = false; + this.processingQueue = []; + } + + private async processEntireFile(filePath: string): Promise { + try { + // Reset state before processing entire file + this.reset(); + + const fileStream = createReadStream(filePath); + const rl = createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + this.currentPosition = 0; + + for await (const line of rl) { + this.currentPosition += Buffer.byteLength(line) + 1; // +1 for newline + await this.processLogLine(line); + } + + // Emit state update after processing entire file + this.emit("state:updated", { type: "full" }); + } catch (error) { + if (error instanceof Error) { + this.emit("error", new Error(`Failed to process entire file: ${error.message}`)); + } + throw error; + } + } + + private async processFileChanges(filePath: string): Promise { + try { + const fileStream = createReadStream(filePath, { + start: this.currentPosition, + }); + + const rl = createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + let hasUpdates = false; + for await (const line of rl) { + this.currentPosition += Buffer.byteLength(line) + 1; // +1 for newline + await this.processLogLine(line); + hasUpdates = true; + } + + if (hasUpdates) { + this.emit("state:updated", { type: "full" }); + } + } catch (error) { + if (error instanceof Error) { + this.emit("error", new Error(`Failed to process file changes: ${error.message}`)); + } + throw error; + } + } + + private async processLogLine(line: string): Promise { + this.emit("log:new_line", line); + try { + let parsed: unknown; + try { + parsed = JSON.parse(line); + } catch (e) { + console.error(e); + this.emit("error", new Error(`Failed to parse JSON: ${line}`)); + return; + } + + // Try parsing as init log first + const initResult = LogInitSchema.safeParse(parsed); + if (initResult.success) { + this.reset(); + this.emit("log:reset"); + return; + } + + // If not init log, try parsing as update + const updateResult = this.updateSchema.safeParse(parsed); + if (updateResult.success) { + this.processStateUpdate(updateResult.data.data); + return; + } + + this.emit("error", new Error(`Invalid log entry - neither init nor update: ${line}`)); + } catch (error) { + if (error instanceof Error) { + this.emit("error", new Error(`Failed to process log line: ${error.message}`)); + } + } + } + + protected abstract processStateUpdate(data: z.infer): void; + protected abstract reset(): void; +} diff --git a/src/base/audit-log.ts b/src/base/state/base-state-logger.ts similarity index 66% rename from src/base/audit-log.ts rename to src/base/state/base-state-logger.ts index bde88f5..25bda56 100644 --- a/src/base/audit-log.ts +++ b/src/base/state/base-state-logger.ts @@ -1,18 +1,9 @@ -import { appendFileSync, existsSync, renameSync, writeFileSync } from "fs"; +import { appendFileSync, copyFileSync, existsSync, truncateSync, writeFileSync } from "fs"; import { join } from "path"; +import { z } from "zod"; +import { LogInit, LogUpdate } from "./dto.js"; -export interface LogUpdate { - timestamp: string; - type: TType; - data: TData; -} - -export interface LogInit { - timestamp: string; - type: "@log_init"; -} - -export class BaseAuditLog> { +export class BaseStateLogger { protected logPath: string; constructor(logFileDefaultPath: readonly string[], logFileDefaultName: string, logPath?: string) { @@ -36,10 +27,13 @@ export class BaseAuditLog | Omit) { + protected logUpdate(update: Omit, "timestamp"> | Omit) { const timestamp = new Date().toISOString(); appendFileSync(this.logPath, JSON.stringify({ ...update, timestamp }) + "\n"); } diff --git a/src/base/state/dto.ts b/src/base/state/dto.ts new file mode 100644 index 0000000..3859876 --- /dev/null +++ b/src/base/state/dto.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +export const LogUpdateSchema = (dataSchema: TData) => + z.object({ + timestamp: z.string(), + data: dataSchema, + }); +export type LogUpdate = z.infer>>; + +export const LogInitSchema = z.object({ + timestamp: z.string(), + type: z.literal("@log_init"), +}); +export type LogInit = z.infer; diff --git a/src/base/tools-factory.ts b/src/base/tools-factory.ts index a0978b4..fbf8144 100644 --- a/src/base/tools-factory.ts +++ b/src/base/tools-factory.ts @@ -1,6 +1,6 @@ import { Logger } from "bee-agent-framework"; import { AnyTool } from "bee-agent-framework/tools/base"; -import { AvailableTool } from "src/agents/agent-registry.js"; +import { AvailableTool } from "src/agents/registry/dto.js"; export type ToolFactoryMethod = () => AnyTool; diff --git a/src/helpers/llm.ts b/src/helpers/llm.ts index 7d0e1f7..f5a881b 100644 --- a/src/helpers/llm.ts +++ b/src/helpers/llm.ts @@ -1,16 +1,16 @@ -import { ChatLLM, ChatLLMOutput } from "bee-agent-framework/llms/chat"; -import { getEnv, parseEnv } from "bee-agent-framework/internals/env"; -import { z } from "zod"; -import { WatsonXChatLLM } from "bee-agent-framework/adapters/watsonx/chat"; -import { OpenAIChatLLM } from "bee-agent-framework/adapters/openai/chat"; -import { OllamaChatLLM } from "bee-agent-framework/adapters/ollama/chat"; import { GroqChatLLM } from "bee-agent-framework/adapters/groq/chat"; -import { VertexAIChatLLM } from "bee-agent-framework/adapters/vertexai/chat"; import { IBMVllmChatLLM } from "bee-agent-framework/adapters/ibm-vllm/chat"; -import { Ollama } from "ollama"; +import { OllamaChatLLM } from "bee-agent-framework/adapters/ollama/chat"; +import { OpenAIChatLLM } from "bee-agent-framework/adapters/openai/chat"; +import { VertexAIChatLLM } from "bee-agent-framework/adapters/vertexai/chat"; +import { WatsonXChatLLM } from "bee-agent-framework/adapters/watsonx/chat"; +import { getEnv, parseEnv } from "bee-agent-framework/internals/env"; +import { ChatLLM, ChatLLMOutput } from "bee-agent-framework/llms/chat"; import Groq from "groq-sdk"; -import { AgentKind } from "src/agents/agent-registry.js"; +import { Ollama } from "ollama"; import OpenAI from "openai"; +import { AgentKindEnum } from "src/agents/registry/dto.js"; +import { z } from "zod"; export const Providers = { WATSONX: "watsonx", @@ -24,17 +24,17 @@ export const Providers = { } as const; type Provider = (typeof Providers)[keyof typeof Providers]; -const env = (name: string, type: AgentKind) => `${name}_${type.toUpperCase()}`; +const env = (name: string, type: AgentKindEnum) => `${name}_${type.toUpperCase()}`; -export const LLMFactories: Record ChatLLM> = { - [Providers.GROQ]: (type: AgentKind) => +export const LLMFactories: Record ChatLLM> = { + [Providers.GROQ]: (type: AgentKindEnum) => new GroqChatLLM({ modelId: getEnv(env(`GROQ_MODEL`, type)) || "llama-3.1-70b-versatile", client: new Groq({ apiKey: getEnv("GROQ_API_KEY"), }), }), - [Providers.OPENAI]: (type: AgentKind) => + [Providers.OPENAI]: (type: AgentKindEnum) => new OpenAIChatLLM({ modelId: getEnv(env("OPENAI_MODEL", type)) || "gpt-4o", parameters: { @@ -42,7 +42,7 @@ export const LLMFactories: Record ChatLLM + [Providers.IBM_RITS]: (type: AgentKindEnum) => new OpenAIChatLLM({ client: new OpenAI({ baseURL: getEnv(env("IBM_RITS_URL", type)), @@ -53,7 +53,7 @@ export const LLMFactories: Record ChatLLM + [Providers.OLLAMA]: (type: AgentKindEnum) => new OllamaChatLLM({ modelId: getEnv(env("OLLAMA_MODEL", type)) || "llama3.1:8b", parameters: { @@ -63,7 +63,7 @@ export const LLMFactories: Record ChatLLM + [Providers.WATSONX]: (type: AgentKindEnum) => WatsonXChatLLM.fromPreset( getEnv(env("WATSONX_MODEL", type)) || "meta-llama/llama-3-1-70b-instruct", { @@ -72,7 +72,7 @@ export const LLMFactories: Record ChatLLM + [Providers.AZURE]: (type: AgentKindEnum) => new OpenAIChatLLM({ modelId: getEnv(env("OPENAI_MODEL", type)) || "gpt-4o-mini", azure: true, @@ -81,18 +81,18 @@ export const LLMFactories: Record ChatLLM + [Providers.VERTEXAI]: (type: AgentKindEnum) => new VertexAIChatLLM({ modelId: getEnv(env("VERTEXAI_MODEL", type)) || "gemini-1.5-flash-001", location: getEnv("VERTEXAI_LOCATION") || "us-central1", project: getEnv("VERTEXAI_PROJECT"), parameters: {}, }), - [Providers.IBM_VLLM]: (type: AgentKind) => + [Providers.IBM_VLLM]: (type: AgentKindEnum) => IBMVllmChatLLM.fromPreset(getEnv(env("IBM_VLLM_MODEL", type))), }; -export function getChatLLM(type: AgentKind, provider?: Provider): ChatLLM { +export function getChatLLM(type: AgentKindEnum, provider?: Provider): ChatLLM { if (!provider) { provider = parseEnv("LLM_BACKEND", z.nativeEnum(Providers), Providers.OLLAMA); } diff --git a/src/helpers/tmux-logger copy.ts b/src/helpers/tmux-logger copy.ts deleted file mode 100644 index e528439..0000000 --- a/src/helpers/tmux-logger copy.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { pino } from "pino"; -import fs from "fs"; -import { AgentKind } from "src/agents/agent-registry.js"; - -// Ensure logs directory exists -if (!fs.existsSync("logs")) { - fs.mkdirSync("logs"); -} - -// Create separate log files for different components -const supervisorLogger = pino( - { - level: process.env.LOGGER_LEVEL || "info", - }, - pino.destination("logs/supervisor_agents.log"), -); - -const registryLogger = pino( - { - level: process.env.LOGGER_LEVEL || "info", - }, - pino.destination("logs/agent_registry.log"), -); - -const taskManagerLogger = pino( - { - level: process.env.LOGGER_LEVEL || "info", - }, - pino.destination("logs/task_manager.log"), -); - -// Cache for operator loggers -const operatorLoggers = new Map(); - -export enum LoggerType { - AGENT = "agent", - REGISTRY = "registry", - TASK_MANAGER = "taskManager", -} - -export function getLogger(type: LoggerType, agentKind: AgentKind | null = null, operatorId = null) { - switch (type) { - case "registry": - return registryLogger; - case "taskManager": - return taskManagerLogger; - case "agent": - switch (agentKind) { - case "supervisor": - return supervisorLogger; - case "operator": - // If operatorId is provided, create/get specific operator logger - if (operatorId !== null) { - if (!operatorLoggers.has(operatorId)) { - operatorLoggers.set( - operatorId, - pino( - { level: process.env.LOGGER_LEVEL || "info" }, - pino.destination(`logs/operator_${operatorId}_agents.log`), - ), - ); - } - return operatorLoggers.get(operatorId); - } - // Default operator logger for backward compatibility - return pino( - { level: process.env.LOGGER_LEVEL || "info" }, - pino.destination("logs/operator_1_agents.log"), - ); - default: - return pino({ level: process.env.LOGGER_LEVEL || "info" }); - } - - default: - return pino({ level: process.env.LOGGER_LEVEL || "info" }); - } -} - -// Cleanup function to close all file descriptors -export function cleanup() { - supervisorLogger.flush(); - registryLogger.flush(); - taskManagerLogger.flush(); - operatorLoggers.forEach((logger) => logger.flush()); -} - -// Handle process termination -process.on("beforeExit", cleanup); -process.on("SIGINT", () => { - cleanup(); - process.exit(0); -}); diff --git a/src/helpers/tmux-logger.ts b/src/helpers/tmux-logger.ts deleted file mode 100644 index fe2bd56..0000000 --- a/src/helpers/tmux-logger.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { pino, Logger } from "pino"; -import fs from "fs"; -import { AgentKind } from "src/agents/agent-registry.js"; - -// Ensure logs directory exists -if (!fs.existsSync("logs")) { - fs.mkdirSync("logs"); -} - -// Create separate log files for different components -const supervisorLogger: Logger = pino( - { - level: process.env.LOGGER_LEVEL || "info", - }, - pino.destination("logs/supervisor_agents.log"), -); - -const registryLogger: Logger = pino( - { - level: process.env.LOGGER_LEVEL || "info", - }, - pino.destination("logs/agent_registry.log"), -); - -const taskManagerLogger: Logger = pino( - { - level: process.env.LOGGER_LEVEL || "info", - }, - pino.destination("logs/task_manager.log"), -); - -// Cache for operator loggers -const operatorLoggers = new Map(); - -export enum LoggerType { - AGENT = "agent", - REGISTRY = "registry", - TASK_MANAGER = "taskManager", -} - -/** - * Get a logger instance for a specific component - * @param type - The type of logger to get - * @param operatorId - Optional operator ID for operator-specific loggers - * @returns A pino logger instance - */ -export function getLogger( - type: LoggerType, - agentKind?: AgentKind, - operatorId?: number | null, -): Logger { - switch (type) { - case "registry": - return registryLogger; - case "taskManager": - return taskManagerLogger; - case "agent": - switch (agentKind) { - case "supervisor": - return supervisorLogger; - case "operator": - // If operatorId is provided, create/get specific operator logger - if (operatorId !== null && operatorId !== undefined) { - if (!operatorLoggers.has(operatorId)) { - operatorLoggers.set( - operatorId, - pino( - { level: process.env.LOGGER_LEVEL || "info" }, - pino.destination(`logs/operator_${operatorId}_agents.log`), - ), - ); - } - return operatorLoggers.get(operatorId)!; - } - // Default operator logger for backward compatibility - return pino( - { level: process.env.LOGGER_LEVEL || "info" }, - pino.destination("logs/operator_1_agents.log"), - ); - default: - return pino({ level: process.env.LOGGER_LEVEL || "info" }); - } - break; - - default: - return pino({ level: process.env.LOGGER_LEVEL || "info" }); - } -} - -/** - * Cleanup function to close all file descriptors - */ -export function cleanup(): void { - supervisorLogger.flush(); - registryLogger.flush(); - taskManagerLogger.flush(); - operatorLoggers.forEach((logger) => logger.flush()); -} - -// Handle process termination -process.on("beforeExit", cleanup); -process.on("SIGINT", () => { - cleanup(); - process.exit(0); -}); diff --git a/src/agent.ts b/src/main.ts similarity index 69% rename from src/agent.ts rename to src/main.ts index 0d296fe..437246e 100644 --- a/src/agent.ts +++ b/src/main.ts @@ -3,30 +3,27 @@ import { FrameworkError } from "bee-agent-framework/errors"; import "dotenv/config.js"; import { createAgent } from "./agents/agent-factory.js"; -import { AgentKindSchema, AgentRegistry } from "./agents/agent-registry.js"; import * as operator from "./agents/operator.js"; +import { AgentKindEnumSchema } from "./agents/registry/dto.js"; +import { AgentRegistry } from "./agents/registry/registry.js"; +import { agentStateLogger } from "./agents/state/logger.js"; import * as supervisor from "./agents/supervisor.js"; import { createConsoleReader } from "./helpers/reader.js"; -import { TaskManager } from "./tasks/task-manager.js"; -import { getLogger, LoggerType } from "./helpers/tmux-logger.js"; -import { agentIdToString } from "./agents/agent-id.js"; -import { getAgentStateLogger } from "./agents/agent-state-logger.js"; -import { getTaskStateLogger } from "./tasks/task-state-logger.js"; +import { TaskManager } from "./tasks/manager/manager.js"; +import { taskStateLogger } from "./tasks/state/logger.js"; // Reset audit logs -getAgentStateLogger(); -getTaskStateLogger(); +agentStateLogger(); +taskStateLogger(); const registry = new AgentRegistry({ agentLifecycle: { async onCreate( config, - poolStats, + agentId, toolsFactory, ): Promise<{ agentId: string; instance: BeeAgent }> { - const { agentKind: agentKind, agentType: agentType, instructions, description } = config; - const num = poolStats.created + 1; - const agentId = agentIdToString({ agentKind, agentType, num }); + const { agentKind, agentType, instructions, description } = config; const tools = config.tools == null ? toolsFactory.getAvailableToolsNames() : config.tools; const instance = createAgent( { @@ -46,17 +43,17 @@ const registry = new AgentRegistry({ instance.destroy(); }, }, - onAgentTypeRegistered(agentKind, agentType) { + onAgentConfigCreated(agentKind, agentType) { taskManager.registerAgentType(agentKind, agentType); }, }); const taskManager = new TaskManager( - async (task, taskManager, { onAgentCreate, onAgentComplete, onAgentError }) => { - const agent = await registry.acquireAgent(task.agentKind, task.agentType); - onAgentCreate(task.id, agent.agentId, taskManager); + async (taskRun, taskManager, { onAgentCreate, onAgentComplete, onAgentError }) => { + const agent = await registry.acquireAgent(taskRun.config.agentKind, taskRun.config.agentType); + onAgentCreate(taskRun.taskRunId, agent.agentId, taskManager); const { instance } = agent; - const prompt = task.input; + const prompt = taskRun.taskRunInput; instance .run( { prompt }, @@ -82,11 +79,10 @@ const taskManager = new TaskManager( ); }); }) - .then((resp) => onAgentComplete(resp.result.text, task.id, agent.agentId, taskManager)) - .catch((err) => onAgentError(err, task.id, agent.agentId, taskManager)) - .finally(async () => { - await registry.releaseAgent(agent.agentId); - }); + .then((resp) => + onAgentComplete(resp.result.text, taskRun.taskRunId, agent.agentId, taskManager), + ) + .catch((err) => onAgentError(err, taskRun.taskRunId, agent.agentId, taskManager)); }, ); @@ -95,20 +91,23 @@ registry.registerToolsFactories([ ["operator", new operator.ToolsFactory()], ]); -registry.registerAgentType({ +registry.createAgentConfig({ autoPopulatePool: false, - agentKind: AgentKindSchema.Enum.supervisor, + agentKind: AgentKindEnumSchema.Enum.supervisor, agentType: supervisor.AgentTypes.BOSS, instructions: "", + tools: registry.getToolsFactory(AgentKindEnumSchema.Enum.supervisor).getAvailableToolsNames(), description: "The boss supervisor agent that control whole app.", maxPoolSize: 1, }); -const { instance: supervisorAgent } = await registry.acquireAgent( - AgentKindSchema.Enum.supervisor, +const { instance: supervisorAgent, agentId: supervisorAgentId } = await registry.acquireAgent( + AgentKindEnumSchema.Enum.supervisor, supervisor.AgentTypes.BOSS, ); +taskManager.registerAdminAgent(supervisorAgentId); + // Can you create tasks to write poem about: sun, earth, mars and assign them to the right agent type and run them? // Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types run all tasks and give me the created poems when it will be all finished? // Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types? @@ -121,7 +120,8 @@ const { instance: supervisorAgent } = await registry.acquireAgent( // Can you generate poem for each of these topics: love, day, night? // Can you get list of articles about each of these topics: deepseek, interstellar engine, agi? -const supervisorLogger = getLogger(LoggerType.AGENT, "supervisor"); +// Can you create different kinds of specialized agents that will do a research on different aspects of person profile from internet? You should be very specific and explanatory in their instructions. Don't create any tasks. +// Base on these agents can you prepare related tasks. And one extra agent and task that will summarize task outputs other tasks. const reader = createConsoleReader({ fallback: "What is the current weather in Las Vegas?" }); for await (const { prompt } of reader) { @@ -141,7 +141,7 @@ for await (const { prompt } of reader) { ) .observe((emitter) => { emitter.on("update", (data, meta) => { - supervisorLogger; + // supervisorLogger; reader.write( `${(meta.creator as any).input.meta.name} 🤖 (${data.update.key}) :`, data.update.value, diff --git a/src/tasks/manager/dto.ts b/src/tasks/manager/dto.ts new file mode 100644 index 0000000..720678c --- /dev/null +++ b/src/tasks/manager/dto.ts @@ -0,0 +1,224 @@ +import { + AgentConfigVersionValueSchema, + AgentIdValueSchema, + AgentKindEnumSchema, + AgentNumValueSchema, + AgentTypeValueSchema, +} from "src/agents/registry/dto.js"; +import { DateStringSchema } from "src/base/dto.js"; +import { z } from "zod"; + +export const TaskKindEnumSchema = AgentKindEnumSchema.describe( + "Specifies the kind of a task in the system. A 'supervisor' kind of tasks are executed by supervisor kind of agents, while a 'operator' handles tasks managed by operators.", +); +export type TaskKindEnum = z.infer; + +export const TaskKindValueSchema = z + .string() + .refine((value) => Object.values(TaskKindEnumSchema.enum).includes(value as TaskKindEnum), { + message: `Task kind must be one of: ${Object.values(TaskKindEnumSchema.enum).join(", ")}`, + }) + .describe( + "Specifies the kind of a task in the system. A 'supervisor' kind of tasks are executed by supervisor kind of agents, while a 'operator' handles tasks managed by operators.", + ); +export type TaskKindValue = z.infer; + +export const TaskTypeValueSchema = z.string().describe("Unique short name of the task type."); +export type TaskTypeValue = z.infer; + +export const TaskRunNumValueSchema = z.number().describe("Task run instance number."); +export type TaskRunNumValue = z.infer; + +export const TaskConfigIdValueSchema = z + .string() + .describe( + "Unique task config id composed of '{taskKind}:{taskType}:{version}' e.g: 'task:poem_generation:1' or 'task:web_scrap:3'", + ); +export type TaskConfigIdValue = z.infer; + +export const TaskConfigVersionValueSchema = z.number().describe("Task config version number."); +export type TaskConfigVersionValue = z.infer; + +export const TaskConcurrencyModeEnumSchema = z + .enum(["EXCLUSIVE", "PARALLEL"]) + .describe( + "Defines whether multiple instances of the task can run simultaneously. EXCLUSIVE allows only one instance to run at a time, while PARALLEL allows multiple concurrent executions", + ); +export type TaskConcurrencyModeEnum = z.infer; + +export const TaskConfigSchema = z + .object({ + taskKind: TaskKindEnumSchema, + taskType: TaskTypeValueSchema, + taskConfigId: TaskConfigIdValueSchema, + taskConfigVersion: z.number().describe("Version number for the task configuration"), + taskConfigInput: z + .string() + .describe( + "Task config input should serves as a template for task run input for derived task runs.", + ), + description: z.string().describe("Detail information about the task and its context"), + intervalMs: z.number().describe("Interval between task executions in milliseconds"), + runImmediately: z.boolean().describe("Whether to run the task immediately upon starting"), + maxRetries: z + .number() + .describe( + "Maximum number of retry attempts if task execution fails. undefined if no retries.", + ) + .nullish(), + retryDelayMs: z.number().describe("Delay between retry attempts in milliseconds").nullish(), + ownerAgentId: z.string().describe("Identifier of who owns/manages this task"), + agentKind: AgentKindEnumSchema, + agentType: AgentTypeValueSchema, + agentNum: AgentNumValueSchema, + agentVersion: AgentConfigVersionValueSchema, + concurrencyMode: TaskConcurrencyModeEnumSchema, + maxRepeats: z + .number() + .describe( + "Maximum number of times the task can execute. If not set, the task can run indefinitely", + ) + .nullish(), + }) + .describe("Represents a periodic task configuration."); + +export type TaskConfig = z.infer; + +export const TaskRunTerminalStatusEnumSchema = z.enum(["STOPPED", "FAILED", "COMPLETED"]); +export type TaskRunTerminalStatusEnum = z.infer; + +export const TaskRunHistoryEntrySchema = z + .object({ + timestamp: DateStringSchema.describe("When this task execution occurred"), + terminalStatus: TaskRunTerminalStatusEnumSchema, + output: z.unknown().describe("Output produced by the task callback"), + error: z.string().optional().describe("Error message if execution failed"), + runNumber: z.number().describe("Which run number this was (1-based)"), + maxRuns: z + .number() + .describe("Maximum number of times this task should execute. Undefined means infinite runs.") + .nullish(), + retryAttempt: z.number().describe("How many retries were needed for this execution"), + maxRepeats: z + .number() + .describe( + "Maximum number of retry attempts if task execution fails. undefined if no retries.", + ) + .nullish(), + agentId: z.string().nullable().describe("ID of agent that executed the task, if occupied"), + executionTimeMs: z.number().describe("How long the task execution took in milliseconds"), + }) + .describe("Records details about a single execution of a task"); + +export type TaskRunHistoryEntry = z.infer; + +export const TaskRunStatusEnumSchema = z.enum([ + "CREATED", + "SCHEDULED", + "EXECUTING", + "WAITING", + "STOPPED", + "FAILED", + "COMPLETED", +]); +export type TaskRunStatusEnum = z.infer; + +export const TaskRunIdValueSchema = z + .string() + .describe( + "Unique task run id composed of '{taskKind}:{taskType}[{instanceNum}]:{version}' e.g: 'task:poem_generation[1]:1' or 'task:web_scrap[2]:3'", + ); +export type TaskRunIdValue = z.infer; + +export const TaskRunSchema = z + .object({ + taskKind: TaskKindEnumSchema, + taskType: TaskTypeValueSchema, + taskRunId: TaskRunIdValueSchema, + taskRunNum: TaskRunNumValueSchema, + taskConfigVersion: TaskConfigVersionValueSchema, + taskRunInput: z + .string() + .describe( + "Input data specific for this task run should follow task config input of the related task config.", + ), + config: TaskConfigSchema, + status: TaskRunStatusEnumSchema.describe("The status of the task."), + isOccupied: z + .boolean() + .describe("Indicates if the task is currently being operated on by an agent"), + occupiedSince: DateStringSchema.optional() + .nullable() + .describe("Timestamp when the task was marked as occupied. undefined if not occupied"), + startRunAt: DateStringSchema.optional().describe("Timestamp of the execution start."), + lastRunAt: DateStringSchema.optional().describe("Timestamp of the last successful execution"), + nextRunAt: DateStringSchema.optional().describe( + "Expected timestamp of the next scheduled execution", + ), + errorCount: z.number().int().describe("Count of consecutive execution failures"), + currentRetryAttempt: z + .number() + .describe("Current retry count. Maximum retries configured via maxRepeats"), + ownerAgentId: AgentIdValueSchema.describe("ID of the agent who owns/manages this task"), + currentAgentId: z + .string() + .optional() + .nullable() + .describe("ID of the agent currently operating on the task. undefined if not occupied"), + completedRuns: z + .number() + .int() + .describe("Number of times this task has been successfully executed"), + history: z.array(TaskRunHistoryEntrySchema).describe("History of task executions"), + maxHistoryEntries: z + .number() + .optional() + .describe("Maximum number of history entries to keep. Undefined means keep all history."), + }) + .describe("Represents the current status and execution state of a task"); + +export type TaskRun = z.infer; + +export const TaskConfigPoolStatsSchema = z + .object({ + poolSize: z.number(), + created: z.number(), + terminated: z.number(), + completed: z.number(), + running: z.number(), + + waiting: z.number(), + stopped: z.number(), + failed: z.number(), + active: z.number(), + total: z.number(), + }) + .describe("Statistics about task runs"); +export type TaskConfigPoolStats = z.infer; + +// Status helpers +const ACTIVE_STATUSES = [ + TaskRunStatusEnumSchema.enum.SCHEDULED, + TaskRunStatusEnumSchema.enum.WAITING, + TaskRunStatusEnumSchema.enum.EXECUTING, +] as const; +type ActiveStatus = (typeof ACTIVE_STATUSES)[number]; + +export const isTaskRunActiveStatus = (status: TaskRunStatusEnum): status is ActiveStatus => + ACTIVE_STATUSES.includes(status as ActiveStatus); + +const TERMINATION_STATUSES = [ + TaskRunStatusEnumSchema.enum.STOPPED, + TaskRunStatusEnumSchema.enum.FAILED, + TaskRunStatusEnumSchema.enum.COMPLETED, +] as const; +type TerminationStatus = (typeof TERMINATION_STATUSES)[number]; + +export const isTaskRunTerminationStatus = ( + status: TaskRunStatusEnum, +): status is TerminationStatus => TERMINATION_STATUSES.includes(status as TerminationStatus); + +export const ActingAgentIdValueSchema = AgentIdValueSchema.describe( + "ID of the agent performing this operation.", +); +export type ActingAgentIdValue = z.infer; diff --git a/src/tasks/manager/manager.ts b/src/tasks/manager/manager.ts new file mode 100644 index 0000000..2c7771b --- /dev/null +++ b/src/tasks/manager/manager.ts @@ -0,0 +1,1182 @@ +import { FrameworkError } from "bee-agent-framework"; +import { Logger } from "bee-agent-framework/logger/logger"; +import { clone, isNonNullish, omit } from "remeda"; +import { + FULL_ACCESS, + READ_EXECUTE_ACCESS, + READ_ONLY_ACCESS, + READ_WRITE_ACCESS, + ResourcesAccessControl, + WRITE_ONLY_ACCESS, +} from "src/access-control/resources-access-control.js"; +import { agentSomeIdToTypeValue } from "src/agents/agent-id.js"; +import { AgentIdValue, AgentKindEnum, AgentTypeValue } from "src/agents/registry/dto.js"; +import { agentStateLogger } from "src/agents/state/logger.js"; +import { taskStateLogger } from "src/tasks/state/logger.js"; +import { updateDeepPartialObject } from "src/utils/objects.js"; +import { + stringToTaskRun, + taskConfigIdToValue, + taskRunIdToString, + taskSomeIdToTypeValue, +} from "../task-id.js"; +import { + isTaskRunActiveStatus, + isTaskRunTerminationStatus, + TaskConfig, + TaskConfigIdValue, + TaskConfigPoolStats, + TaskConfigVersionValue, + TaskKindEnum, + TaskKindEnumSchema, + TaskRun, + TaskRunHistoryEntry, + TaskRunIdValue, + TaskRunTerminalStatusEnum, + TaskTypeValue, +} from "./dto.js"; + +export type TaskRunRuntime = TaskRun & { + intervalId: NodeJS.Timeout | null; +}; + +const TASK_MANAGER_RESOURCE = "task_manager"; +const TASK_MANAGER_USER = "task_manager_user"; + +const MAX_POOL_SIZE = 100; + +export class TaskManager { + private readonly logger: Logger; + /** Map of registered task type and their configurations */ + private taskConfigs: Map>; + private taskRuns = new Map(); + /** Map of task run pools by task config and task run IDs */ + private taskPools: Map< + TaskKindEnum, + Map][]> + >; + private scheduledTasksToStart: { taskRunId: TaskRunIdValue; actingAgentId: AgentIdValue }[] = []; + private taskStartIntervalId: NodeJS.Timeout | null = null; + private registeredAgentTypes = new Map(); + private ac: ResourcesAccessControl; + + constructor( + private onTaskStart: ( + taskRun: TaskRun, + taskManager: TaskManager, + callbacks: { + onAgentCreate: ( + taskRunId: TaskRunIdValue, + agentId: AgentIdValue, + taskManage: TaskManager, + ) => void; + onAgentComplete: ( + output: string, + taskRunId: TaskRunIdValue, + agentId: AgentIdValue, + taskManage: TaskManager, + ) => void; + onAgentError: ( + err: Error, + taskRunId: TaskRunIdValue, + agentId: AgentIdValue, + taskManage: TaskManager, + ) => void; + }, + ) => Promise, + private options: { + errorHandler?: (error: Error, taskRunId: TaskRunIdValue) => void; + occupancyTimeoutMs?: number; + adminIds?: string[]; + maxHistoryEntries?: number; + } = {}, + ) { + this.logger = Logger.root.child({ name: "TaskManager" }); + this.logger.info("Initializing TaskManager"); + + this.ac = new ResourcesAccessControl(this.constructor.name, [TASK_MANAGER_USER]); + + this.ac.createResource(TASK_MANAGER_RESOURCE, TASK_MANAGER_USER, TASK_MANAGER_USER); + + this.options = { + errorHandler: (error: Error, taskRunId: TaskRunIdValue) => { + this.logger.error("Task error occurred", { taskRunId, error }); + }, + occupancyTimeoutMs: 30 * 60 * 1000, + adminIds: [], + maxHistoryEntries: 100, // Default to keeping last 100 entries + ...options, + }; + + // Initialize task pools for all task kinds + this.taskConfigs = new Map(TaskKindEnumSchema.options.map((kind) => [kind, new Map()])); + this.taskPools = new Map(TaskKindEnumSchema.options.map((kind) => [kind, new Map()])); + + this.taskStartIntervalId = setInterval(async () => { + try { + await this.processNextStartTask(); // Your async function + } catch (err) { + this.logger.error("Process next start task error", err); + } + }, 100); // Runs every 100ms (0.1 second) + } + + registerAdminAgent(agentId: AgentIdValue) { + this.ac.createPermissions(TASK_MANAGER_RESOURCE, agentId, FULL_ACCESS, TASK_MANAGER_USER); + } + + registerAgentType(agentKind: AgentKindEnum, agentType: string): void { + let types = this.registeredAgentTypes.get(agentKind); + if (!types) { + types = [agentType]; + this.registeredAgentTypes.set(agentKind, types); + } else { + if (types.includes(agentType)) { + throw new Error(`Agent type duplicity for agentKind:${agentKind}, agentType:${agentType}`); + } + types.push(agentType); + } + taskStateLogger().logAgentTypeRegister({ + agentTypeId: agentSomeIdToTypeValue({ agentKind, agentType }), + }); + } + + getTaskRun( + taskRunId: TaskRunIdValue, + actingAgentId: AgentIdValue, + permissions = READ_ONLY_ACCESS, + ): TaskRunRuntime { + this.logger.trace("Getting task run by ID", { taskRunId }); + this.ac.checkPermission(taskRunId, actingAgentId, permissions); + + const taskRun = this.taskRuns.get(taskRunId); + if (!taskRun) { + this.logger.error("Task run not found", { taskRunId }); + throw new Error(`Task run with ID '${taskRunId}' not found`); + } + return taskRun; + } + + getPoolStats( + taskKind: TaskKindEnum, + taskType: TaskTypeValue, + actingAgentId: AgentIdValue, + ): [TaskConfigPoolStats, [number, TaskConfigPoolStats][]] { + this.logger.trace("Getting pool statistics", { taskKind, taskType, actingAgentId }); + + this.ac.checkPermission(TASK_MANAGER_RESOURCE, actingAgentId, READ_ONLY_ACCESS); + + const pool = this.getTaskTypeVersionSetsArray(taskKind, taskType, false); + if (!pool) { + return [ + { + poolSize: 0, + created: 0, + running: 0, + waiting: 0, + stopped: 0, + failed: 0, + completed: 0, + terminated: 0, + active: 0, + total: 0, + }, + [], + ]; + } + + const versionedTaskRuns = pool.map( + ([version, set]) => + [ + version, + Array.from(set) + .map((t) => { + if (this.ac.hasPermission(t, actingAgentId, READ_ONLY_ACCESS)) { + return this.getTaskRun(t, actingAgentId, READ_ONLY_ACCESS); + } + }) + .filter(isNonNullish), + ] as const, + ); + const versions = versionedTaskRuns.map(([version, taskRuns]) => { + const config = this.getTaskConfig(taskKind, taskType, actingAgentId, version); + const stats = { + poolSize: config.concurrencyMode === "EXCLUSIVE" ? 1 : MAX_POOL_SIZE, + active: taskRuns.filter((t) => isTaskRunActiveStatus(t.status)).length, + terminated: taskRuns.filter((t) => isTaskRunTerminationStatus(t.status)).length, + completed: taskRuns.filter((t) => t.status === "COMPLETED").length, + running: taskRuns.filter((t) => t.status === "EXECUTING").length, + failed: taskRuns.filter((t) => t.status === "FAILED").length, + stopped: taskRuns.filter((t) => t.status === "STOPPED").length, + waiting: taskRuns.filter((t) => t.status === "WAITING").length, + created: taskRuns.filter((t) => t.status === "CREATED").length, + total: taskRuns.length, + } satisfies TaskConfigPoolStats; + return [version, stats] as [number, TaskConfigPoolStats]; + }); + + const stats = versions.reduce( + (prev, [, curr]) => { + const sum = { + poolSize: curr.poolSize + prev.poolSize, + active: curr.active + prev.active, + terminated: curr.terminated + prev.terminated, + completed: curr.completed + prev.completed, + running: curr.running + prev.running, + failed: curr.failed + prev.failed, + stopped: curr.stopped + prev.stopped, + waiting: curr.waiting + prev.waiting, + created: curr.created + prev.created, + total: curr.total + prev.total, + } satisfies TaskConfigPoolStats; + return sum; + }, + { + poolSize: 0, + active: 0, + terminated: 0, + completed: 0, + running: 0, + failed: 0, + stopped: 0, + waiting: 0, + created: 0, + total: 0, + } satisfies TaskConfigPoolStats, + ); + + this.logger.debug("Pool statistics", { taskType: taskType, ...stats }); + return [stats, versions]; + } + + private getPoolStatsByVersion( + taskKind: TaskKindEnum, + taskType: TaskTypeValue, + taskConfigVersion: number, + actingAgentId: AgentIdValue, + ) { + // FIXME Unoptimized + const [, versions] = this.getPoolStats(taskKind, taskType, actingAgentId); + const found = versions.find(([currVersion]) => currVersion === taskConfigVersion); + if (!found) { + return { + poolSize: 0, + active: 0, + terminated: 0, + completed: 0, + running: 0, + failed: 0, + stopped: 0, + waiting: 0, + created: 0, + total: 0, + } satisfies TaskConfigPoolStats; + } + const [, versionStats] = found; + return versionStats; + } + + private getTaskKindPoolMap(taskKind: TaskKindEnum) { + const poolKind = this.taskPools.get(taskKind); + if (!poolKind) { + throw new Error(`There is missing pool for task taskKind:${taskKind}`); + } + return poolKind; + } + + private getTaskTypeVersionSetsArray( + taskKind: TaskKindEnum, + taskType: AgentTypeValue, + throwError = true, + ) { + const poolKind = this.getTaskKindPoolMap(taskKind); + const pool = poolKind.get(taskType); + if (!pool && throwError) { + throw new Error( + `There is missing pool version sets array for agent agentKind:${taskKind} agentType:${taskType}`, + ); + } + return pool; + } + + private getTaskTypeVersionSet( + taskKind: TaskKindEnum, + taskType: TaskTypeValue, + taskConfigVersion: number, + ) { + const poolVersionSetsArray = this.getTaskTypeVersionSetsArray(taskKind, taskType)!; + const poolVersionSet = poolVersionSetsArray.find((it) => it[0] === taskConfigVersion); + if (!poolVersionSet) { + throw new Error( + `There is missing pool version set for task taskKind:${taskKind} taskType:${taskType} version:${taskConfigVersion}`, + ); + } + return poolVersionSet[1]; + } + + createTaskConfig( + config: Omit, + ownerAgentId: string, + actingAgentId: AgentIdValue, + ): TaskConfig { + const { taskKind, taskType, maxRepeats: maxRuns } = config; + this.logger.info("Create new task config", { + taskKind, + taskType, + maxRuns, + }); + this.ac.checkPermission(TASK_MANAGER_RESOURCE, actingAgentId, WRITE_ONLY_ACCESS); + + const taskTypesMap = this.getTaskConfigMap(taskKind); + if (taskTypesMap.has(taskType)) { + this.logger.error("Task type already registered", { taskType }); + throw new Error(`Task type '${taskType}' is already registered`); + } + + if (!this.registeredAgentTypes.get(config.agentKind)?.includes(config.agentType)) { + throw new Error( + `Agent kind: ${config.agentKind} type: ${config.agentType} wasn't yet registered`, + ); + } + + const taskConfigVersion = 1; + const taskConfigId = taskConfigIdToValue({ + ...config, + taskConfigVersion, + }); + const configVersioned = { + ...config, + taskConfigId, + ownerAgentId, + taskConfigVersion, + } satisfies TaskConfig; + taskTypesMap.set(taskType, [configVersioned]); + + // Permissions + this.ac.createResource(taskConfigId, ownerAgentId, actingAgentId); + this.ac.createPermissions(taskConfigId, ownerAgentId, READ_EXECUTE_ACCESS, actingAgentId); + + taskStateLogger().logTaskConfigCreate({ + taskConfigId, + taskType: taskSomeIdToTypeValue(configVersioned), + config: configVersioned, + }); + + this.initializeTaskPool(taskKind, taskType, taskConfigVersion); + + return configVersioned; + } + + private initializeTaskPool(taskKind: TaskKindEnum, taskType: TaskTypeValue, version: number) { + this.logger.debug("Initializing task pool", { + taskKind, + taskType, + version, + }); + + const kindPool = this.getTaskKindPoolMap(taskKind); + let typePool = kindPool.get(taskType); + if (!typePool) { + typePool = []; + kindPool.set(taskType, typePool); + } + typePool.push([version, new Set([])]); + } + + private getTaskConfigMap(taskKind: TaskKindEnum) { + const typesMap = this.taskConfigs.get(taskKind); + if (!typesMap) { + throw new Error(`There is missing types map for task taskKind:${taskKind}`); + } + return typesMap; + } + + private getTaskConfigTypeMap(taskKind: TaskKindEnum, taskType: TaskTypeValue) { + const taskConfigTypeMap = this.getTaskConfigMap(taskKind); + const taskConfigVersions = taskConfigTypeMap.get(taskType); + if (!taskConfigVersions) { + this.logger.error("Task config type map was not found", { taskKind, taskType }); + throw new Error(`Task kind '${taskKind}' type '${taskType}' was not found`); + } + return taskConfigVersions; + } + + getTaskConfig( + taskKind: TaskKindEnum, + taskType: TaskTypeValue, + actingAgentId: AgentIdValue, + taskConfigVersion?: number, + permissions = READ_ONLY_ACCESS, + ): TaskConfig { + const configVersions = this.getTaskConfigMap(taskKind).get(taskType); + if (!configVersions) { + this.logger.error("Task config not found", { taskType }); + throw new Error(`Task kind '${taskKind}' type '${taskType}' was not found`); + } + + let result; + if (taskConfigVersion != null) { + const configVersion = configVersions.find((c) => c.taskConfigVersion === taskConfigVersion); + if (!configVersion) { + throw new Error( + `Task kind '${taskKind}' type '${taskType}' version '${taskConfigVersion}' was not found`, + ); + } + result = configVersion; + } + + const lastConfigVersion = configVersions.at(-1); + if (lastConfigVersion == null) { + throw new Error(`Task kind '${taskKind}' type '${taskType}' last version was not found`); + } + result = lastConfigVersion; + this.ac.checkPermission(lastConfigVersion.taskConfigId, actingAgentId, permissions); + return result; + } + + updateTaskConfig( + update: Pick & + Partial< + Pick< + TaskConfig, + | "description" + | "intervalMs" + | "taskConfigInput" + | "runImmediately" + | "maxRepeats" + | "maxRetries" + | "retryDelayMs" + | "concurrencyMode" + > + >, + actingAgentId: AgentIdValue, + ) { + const { taskKind, taskType } = update; + + const config = this.getTaskConfig( + taskKind, + taskType, + actingAgentId, + undefined, + READ_WRITE_ACCESS, + ); + + const newConfigVersion = clone(config); + + const taskConfigVersion = config.taskConfigVersion + 1; + const taskConfigId = taskConfigIdToValue({ + ...config, + taskConfigVersion, + }); + updateDeepPartialObject(newConfigVersion, { + ...update, + taskConfigId, + taskConfigVersion, + }); + const configVersions = this.getTaskConfigTypeMap(taskKind, taskType); + configVersions.push(newConfigVersion); + + this.ac.createResource(taskConfigId, config.ownerAgentId, actingAgentId); + this.ac.createPermissions( + taskConfigId, + config.ownerAgentId, + READ_EXECUTE_ACCESS, + actingAgentId, + ); + + taskStateLogger().logTaskConfigUpdate({ + taskType: taskSomeIdToTypeValue(newConfigVersion), + taskConfigId: newConfigVersion.taskConfigId, + config: newConfigVersion, + }); + + return newConfigVersion; + } + + destroyTaskConfig( + taskKind: TaskKindEnum, + taskType: TaskTypeValue, + actingAgentId: AgentIdValue, + ): void { + this.logger.trace("Destroying agent configuration", { taskKind, taskType }); + + const configVersions = this.getTaskConfigMap(taskKind).get(taskType); + if (!configVersions) { + this.logger.error("Task config versions was not found", { taskKind, taskType }); + throw new Error(`Task kind '${taskKind}' type '${taskType}' config versions was not found`); + } + + let index = 0; + for (const { taskConfigVersion, taskConfigId } of configVersions) { + this.ac.checkPermission(taskConfigId, actingAgentId, READ_WRITE_ACCESS); + const stats = this.getPoolStatsByVersion( + taskKind, + taskType, + taskConfigVersion, + actingAgentId, + ); + if (stats.active) { + throw new Error( + `Task config kind '${taskKind}' type '${taskType}' version '${taskConfigVersion}' can't be destroyed while it is still has active runs.`, + ); + } + configVersions.splice(index, 1)[0]; + this.logger.info("Task config destroyed successfully", { + taskConfigId, + taskKind, + taskType, + taskConfigVersion, + }); + + taskStateLogger().logTaskConfigDestroy({ + taskConfigId, + taskType, + }); + + this.ac.removeResource(taskConfigId, actingAgentId); + + const [poolStats, versions] = this.getPoolStats(taskKind, taskType, actingAgentId); + taskStateLogger().logPoolChange({ + taskTypeId: taskSomeIdToTypeValue({ taskKind, taskType }), + poolStats, + versions, + }); + + index++; + } + + if (!configVersions.length) { + this.getTaskConfigMap(taskKind).delete(taskType); + this.ac.removeResource(taskSomeIdToTypeValue({ taskKind, taskType }), actingAgentId); + } + + if (!this.getTaskConfigMap(taskKind).size) { + this.taskConfigs.delete(taskKind); + } + } + + createTaskRun( + taskKind: TaskKindEnum, + taskType: TaskTypeValue, + taskRunInput: string, + actingAgentId: AgentIdValue, + ): TaskRun { + this.logger.debug("Creating new task run", { + taskKind, + taskType, + actingAgentId, + }); + + const config = this.getTaskConfig( + taskKind, + taskType, + actingAgentId, + undefined, + READ_EXECUTE_ACCESS, + ); + + this.ac.checkPermission(config.taskConfigId, actingAgentId, READ_EXECUTE_ACCESS); + + const { taskConfigVersion } = config; + const versionPoolStats = this.getPoolStatsByVersion( + taskKind, + taskType, + taskConfigVersion, + actingAgentId, + ); + const taskRunNum = versionPoolStats.created + 1; + const taskRunId = taskRunIdToString({ + taskKind, + taskType, + taskRunNum, + taskConfigVersion, + }); + + const types = this.registeredAgentTypes.get(config.agentKind); + if (!types || !types.includes(config.agentType)) { + throw new Error( + `Unregistered task type for task.agentKind:${config.agentKind} task.agentType: ${config.agentType}`, + ); + } + + const taskRun: TaskRun = { + taskKind, + taskType, + taskRunId, + taskConfigVersion: config.taskConfigVersion, + taskRunNum, + taskRunInput: taskRunInput, + config: clone(config), + status: "CREATED", + currentRetryAttempt: 0, + isOccupied: false, + errorCount: 0, + ownerAgentId: config.ownerAgentId, + completedRuns: 0, + history: [], + }; + this.taskRuns.set(taskRunId, { + intervalId: null, + ...taskRun, + }); + + taskStateLogger().logTaskRunCreate({ + taskConfigId: taskRun.config.taskConfigId, + taskRunId, + taskRun, + }); + + this.ac.createResource(taskRunId, actingAgentId, actingAgentId); + + const pool = this.getTaskTypeVersionSetsArray(taskKind, taskType)!; + let poolVersionSetArrayItem = pool.find((p) => p[0] === taskConfigVersion); + if (!poolVersionSetArrayItem) { + poolVersionSetArrayItem = [taskConfigVersion, new Set([])]; + pool.push(poolVersionSetArrayItem); + } + poolVersionSetArrayItem[1].add(taskRunId); + this.logger.trace("Added task to pool", { taskKind, taskType, taskConfigVersion, taskRunId }); + + const [poolStats, versions] = this.getPoolStats(taskKind, taskType, actingAgentId); + taskStateLogger().logPoolChange({ + taskTypeId: taskSomeIdToTypeValue({ taskKind, taskType }), + poolStats, + versions, + }); + + if (config.runImmediately) { + this.scheduleStartTaskRun(taskRunId, actingAgentId); + } + + return taskRun; + } + + /** + * Schedule task to start as soon as possible. + * Only owners and admins can start/stop tasks. + */ + scheduleStartTaskRun(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): void { + this.logger.info("Schedule task run start", { taskRunId, actingAgentId }); + this.ac.checkPermission(taskRunId, actingAgentId, FULL_ACCESS); + + const taskRun = this.taskRuns.get(taskRunId); + if (!taskRun) { + this.logger.error("Task run not found", { taskRunId }); + throw new Error(`Task run ${taskRunId} not found`); + } + + const { taskKind, taskType, taskConfigVersion } = taskRun; + + const versionPoolStats = this.getPoolStatsByVersion( + taskKind, + taskType, + taskConfigVersion, + actingAgentId, + ); + + if (versionPoolStats.active >= versionPoolStats.poolSize) { + this.logger.trace("Task pool population is full", { taskKind, taskType, taskConfigVersion }); + return; + } + + this._updateTaskRun(taskRunId, taskRun, { + status: "SCHEDULED", + }); + this.scheduledTasksToStart.push({ taskRunId, actingAgentId }); + } + + async processNextStartTask() { + if (!this.scheduledTasksToStart.length) { + return; + } + const { taskRunId, actingAgentId } = this.scheduledTasksToStart.shift()!; + + this.logger.info("Starting scheduled task run", { taskRunId, actingAgentId }); + + const taskRun = this.getTaskRun(taskRunId, actingAgentId, FULL_ACCESS); + if (!taskRun) { + this.logger.error("Task not found", { taskRunId }); + throw new Error(`Task ${taskRunId} not found`); + } + + if (taskRun.status === "EXECUTING") { + this.logger.warn("Task is already executing", { taskRunId }); + throw new Error(`Task ${taskRunId} is already executing`); + } + + if (taskRun.config.runImmediately || !taskRun.config.intervalMs) { + this.logger.debug("Executing task immediately", { taskRunId }); + await this.executeTask(taskRunId, actingAgentId); + } + + if ( + taskRun.config.intervalMs && + (taskRun.config.maxRepeats == null || taskRun.completedRuns < taskRun.config.maxRepeats) + ) { + this.logger.debug("Setting up task interval", { + taskRunId, + intervalMs: taskRun.config.intervalMs, + }); + const self = this; + taskRun.intervalId = setInterval(async () => { + self.executeTask(taskRunId, actingAgentId); + }, taskRun.config.intervalMs); + + this._updateTaskRun(taskRunId, taskRun, { + status: "WAITING", + nextRunAt: new Date(Date.now() + taskRun.config.intervalMs), + }); + } + + this.logger.info("Task started successfully", { taskRunId }); + } + + stopTaskRun(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue, isCompleted = false): void { + this.logger.info("Stopping task", { taskRunId, actingAgentId }); + this.ac.checkPermission( + taskSomeIdToTypeValue(stringToTaskRun(taskRunId)), + actingAgentId, + READ_WRITE_ACCESS, + ); + + const taskRun = this.getTaskRun(taskRunId, actingAgentId); + if (taskRun.status === "STOPPED") { + this.logger.debug("Task already stopped", { taskRunId }); + return; + } + + if (taskRun.intervalId) { + this.logger.debug("Clearing task interval", { taskRunId }); + clearInterval(taskRun.intervalId); + taskRun.intervalId = null; + } + + if (taskRun.isOccupied) { + this.logger.debug("Releasing task occupancy before stop", { taskRunId }); + this.releaseTaskRunOccupancy(taskRunId, actingAgentId); + } + + this._updateTaskRun(taskRunId, taskRun, { + status: isCompleted ? "COMPLETED" : "STOPPED", + nextRunAt: undefined, + }); + this.logger.info("Task stopped successfully", { taskRunId }); + } + + destroyTaskRun(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): void { + this.logger.info("Attempting to destroy task run", { taskRunId, actingAgentId }); + this.ac.checkPermission(taskRunId, actingAgentId, WRITE_ONLY_ACCESS); + + const taskRun = this.taskRuns.get(taskRunId); + if (!taskRun) { + this.logger.error("Task run not found for destruction", { taskRunId }); + throw new Error(`Task run with ID '${taskRunId}' not found`); + } + + if (taskRun.status === "EXECUTING") { + this.logger.debug("Stopping executing task before removal", { taskRunId }); + this.stopTaskRun(taskRunId, actingAgentId); + } + + if (taskRun.isOccupied) { + this.logger.debug("Releasing task occupancy before removal", { taskRunId }); + this.releaseTaskRunOccupancy(taskRunId, actingAgentId); + } + + const { + config: { taskKind, taskType, taskConfigVersion }, + } = taskRun; + + // Remove from pool if it's in one + const poolSet = this.getTaskTypeVersionSet(taskKind, taskType, taskConfigVersion); + if (poolSet) { + poolSet.delete(taskRunId); + this.logger.trace("Removed task run from pool", { + taskRunId, + taskKind, + taskType, + taskConfigVersion, + }); + } else { + throw new Error(`Missing pool`); + } + + if (!poolSet.size) { + // Remove pool version array set item + const poolVersionSetsArray = this.getTaskTypeVersionSetsArray(taskKind, taskType)!; + const poolVersionSet = poolVersionSetsArray.findIndex((it) => it[0] === taskConfigVersion); + poolVersionSetsArray.splice(poolVersionSet, 1); + } + + this.taskRuns.delete(taskRunId); + this.logger.info("Task run destroyed successfully", { + taskKind, + taskType, + taskConfigVersion, + }); + + taskStateLogger().logTaskRunDestroy({ + taskRunId, + }); + + const [poolStats, versions] = this.getPoolStats(taskKind, taskType, actingAgentId); + taskStateLogger().logPoolChange({ + taskTypeId: taskSomeIdToTypeValue(taskRun), + poolStats, + versions, + }); + } + + /** + * Sets task as occupied. + * Only authorized agents can occupy tasks. + */ + private setTaskRunOccupied(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): boolean { + this.logger.info("Setting task run as occupied", { taskRunId, agentId: actingAgentId }); + this.ac.createPermissions(taskRunId, actingAgentId, FULL_ACCESS, TASK_MANAGER_USER); + + const taskRun = this.getTaskRun(taskRunId, actingAgentId); + if (taskRun.isOccupied) { + this.logger.debug("Task not available for occupancy", { + taskRunId, + exists: !!taskRun, + }); + return false; + } + + const occupiedSince = new Date(); + this._updateTaskRun(taskRunId, taskRun, { + isOccupied: true, + occupiedSince, + currentAgentId: actingAgentId, + }); + + agentStateLogger().logTaskAssigned({ + agentId: actingAgentId, + assignment: clone(omit(taskRun, ["intervalId"])), + assignmentId: taskRunId, + assignedSince: occupiedSince, + }); + + if (this.options.occupancyTimeoutMs) { + this.logger.debug("Setting occupancy timeout", { + taskRunId, + timeoutMs: this.options.occupancyTimeoutMs, + }); + setTimeout(() => { + this.releaseTaskRunOccupancy(taskRunId, actingAgentId); + }, this.options.occupancyTimeoutMs); + } + + this.logger.info("Task occupied successfully", { taskRunId, agentId: actingAgentId }); + return true; + } + + /** + * Releases task run occupancy. + * Only the current agent or owners can release occupancy. + */ + private releaseTaskRunOccupancy(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): boolean { + this.logger.info("Releasing task occupancy", { taskRunId, agentId: actingAgentId }); + + const taskRun = this.getTaskRun(taskRunId, actingAgentId); + if (!taskRun || !taskRun.isOccupied) { + this.logger.debug("Task not available for release", { taskRunId, exists: !!taskRun }); + return false; + } + + const unassignedAt = new Date(); + this._updateTaskRun(taskRunId, taskRun, { + isOccupied: false, + occupiedSince: null, + currentAgentId: null, + }); + + this.ac.removePermissions(taskRunId, actingAgentId, TASK_MANAGER_USER); + + agentStateLogger().logTaskUnassigned({ + agentId: actingAgentId, + assignmentId: taskRunId, + unassignedAt, + }); + + this.logger.info("Task occupancy released successfully", { taskRunId }); + return true; + } + + /** + * Update task status + */ + updateTaskRun( + taskRunId: TaskRunIdValue, + update: Partial>, + actingAgentId: AgentIdValue, + ) { + this.logger.info("Updating task status", { taskRunId, actingAgentId, update }); + const status = this.getTaskRun(taskRunId, actingAgentId, WRITE_ONLY_ACCESS); + return this._updateTaskRun(taskRunId, status, update); + } + + // Just for auditlog + private _updateTaskRun( + taskRunId: TaskRunIdValue, + taskRun: TaskRun, + update: Partial, + ): TaskRun { + updateDeepPartialObject(taskRun, update); + taskStateLogger().logTaskRunUpdate({ taskRunId, taskRun: update }); + if (update.status) { + const { taskKind, taskType } = taskRun; + const [poolStats, versions] = this.getPoolStats(taskKind, taskType, TASK_MANAGER_USER); + taskStateLogger().logPoolChange({ + taskTypeId: taskSomeIdToTypeValue({ taskKind, taskType }), + poolStats, + versions, + }); + } + return taskRun; + } + + /** + * Gets all task runs visible to the agent. + */ + getAllTaskRuns(agentId: AgentIdValue): TaskRun[] { + this.logger.info("Getting all task runs", { agentId }); + + const taskRuns = Array.from(this.taskRuns.values()).filter((taskRun) => + this.ac.hasPermission(taskRun.taskRunId, agentId, READ_ONLY_ACCESS), + ); + + this.logger.debug("Retrieved task runs", { agentId, count: taskRuns.length }); + return taskRuns; + } + + /** + * Checks if a task is currently occupied. + */ + isTaskRunOccupied(taskRunId: TaskRunIdValue, agentId: AgentIdValue): boolean { + this.logger.debug("Checking task occupancy", { taskRunId, agentId }); + this.ac.checkPermission(taskRunId, agentId, READ_ONLY_ACCESS); + + const task = this.taskRuns.get(taskRunId); + if (!task) { + this.logger.error("Task not found", { taskRunId }); + throw new Error(`Undefined taskRunId: ${taskRunId}`); + } + + return task.isOccupied; + } + + /** + * Executes a task with retry logic and records history. + * @private + */ + private async executeTask(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): Promise { + this.logger.info("Executing task", { taskRunId, agentId: actingAgentId }); + this.ac.checkPermission(taskRunId, actingAgentId, READ_EXECUTE_ACCESS); + + const taskRun = this.getTaskRun(taskRunId, actingAgentId); + const retryAttempt = taskRun.currentRetryAttempt; + if (retryAttempt > 0) { + this.logger.debug("Retry attempt", { retryAttempt, maxRetries: taskRun.config.maxRetries }); + if (!!taskRun.config.maxRetries && retryAttempt >= taskRun.config.maxRetries) { + this.logger.warn("Last retry attempt", { taskRunId }); + } + } + + if (taskRun.status === "COMPLETED" || taskRun.isOccupied) { + this.logger.debug("Skipping task execution", { + taskRunId, + reason: taskRun.status === "COMPLETED" ? "completed" : "occupied", + }); + return; + } + + const startTime = Date.now(); + + this._updateTaskRun(taskRunId, taskRun, { + status: "EXECUTING", + lastRunAt: new Date(), + nextRunAt: + taskRun.config.maxRepeats != null && taskRun.completedRuns < taskRun.config.maxRepeats + ? new Date(Date.now() + taskRun.config.intervalMs) + : undefined, + }); + + this.logger.debug("Executing task callback", { + taskRunId, + lastRunAt: taskRun.lastRunAt, + nextRunAt: taskRun.nextRunAt, + }); + + await this.onTaskStart(taskRun, this, { + onAgentCreate(taskRunId, agentId, taskManager) { + taskManager.setTaskRunOccupied(taskRunId, agentId); + }, + onAgentComplete(output, taskRunId, agentId, taskManager) { + const taskRun = taskManager.getTaskRun(taskRunId, agentId, READ_WRITE_ACCESS); + taskManager._updateTaskRun(taskRunId, taskRun, { + completedRuns: taskRun.completedRuns + 1, + }); + + // Record history entry + taskManager.addHistoryEntry(taskRunId, agentId, { + timestamp: new Date(), + terminalStatus: "COMPLETED", + output, + runNumber: taskRun.completedRuns, + maxRuns: taskRun.config.maxRepeats, + retryAttempt: taskRun.currentRetryAttempt, + maxRepeats: taskRun.config.maxRepeats, + agentId: agentId, + executionTimeMs: Date.now() - startTime, + }); + + taskManager.logger.debug("Task executed successfully", { + taskRunId, + completedRuns: taskRun.completedRuns, + maxRuns: taskRun.config.maxRepeats, + }); + + taskManager.releaseTaskRunOccupancy(taskRunId, agentId); + // Check if we've reached maxRuns + if (taskRun.config.maxRepeats && taskRun.completedRuns >= taskRun.config.maxRepeats) { + taskManager.stopTaskRun(taskRunId, taskRun.config.ownerAgentId); + taskManager.logger.info("Task reached maximum runs and has been stopped", { + taskRunId, + completedRuns: taskRun.completedRuns, + maxRuns: taskRun.config.maxRepeats, + }); + } + }, + async onAgentError(err, taskRunId, agentId, taskManager) { + let error; + if (err instanceof FrameworkError) { + error = err.explain(); + } else { + error = err instanceof Error ? err.message : String(err); + } + + const taskRun = taskManager.getTaskRun(taskRunId, agentId); + taskManager._updateTaskRun(taskRunId, taskRun, { + errorCount: taskRun.errorCount + 1, + completedRuns: taskRun.completedRuns + 1, + }); + const retryAttempt = taskRun.currentRetryAttempt; + + // Record history entry + taskManager.addHistoryEntry(taskRunId, agentId, { + timestamp: new Date(), + terminalStatus: "FAILED", + error, + runNumber: taskRun.completedRuns, + maxRuns: taskRun.config.maxRepeats, + retryAttempt, + maxRepeats: taskRun.config.maxRepeats, + agentId: agentId, + executionTimeMs: Date.now() - startTime, + }); + + taskManager.logger.error(`Task execution failed ${error}`, { + taskRunId, + runNumber: taskRun.completedRuns, + maxRuns: taskRun.config.maxRepeats, + retryAttempt, + maxRepeats: taskRun.config.maxRepeats, + errorCount: taskRun.errorCount, + error, + }); + + if (taskManager.options.errorHandler) { + taskManager.options.errorHandler(err as Error, taskRunId); + } + + taskManager.logger.debug("Releasing task occupancy before removal", { taskRunId }); + taskManager.releaseTaskRunOccupancy(taskRunId, agentId); + if (taskRun.config.maxRepeats) { + if (retryAttempt >= taskRun.config.maxRepeats) { + taskManager.stopTaskRun(taskRunId, taskRun.config.ownerAgentId); + } else { + taskManager._updateTaskRun(taskRunId, taskRun, { + currentRetryAttempt: retryAttempt + 1, + }); + } + } + }, + }); + } + + /** + * Add a history entry for a task + * @private + */ + private addHistoryEntry( + taskRunId: TaskRunIdValue, + actingAgentId: AgentIdValue, + entry: TaskRunHistoryEntry, + ): void { + const taskRun = this.getTaskRun(taskRunId, actingAgentId); + taskRun.history.push(entry); + taskStateLogger().logTaskHistoryEntryCreate({ taskRunId, entry }); + agentStateLogger().logTaskHistoryEntry({ + agentId: actingAgentId, + entry, + assignmentId: taskRunId, + }); + + // Trim history if it exceeds maximum entries + const maxEntries = taskRun.maxHistoryEntries ?? this.options.maxHistoryEntries; + if (maxEntries && taskRun.history.length > maxEntries) { + taskRun.history = taskRun.history.slice(-maxEntries); + } + } + + /** + * Gets task history entries + * Agents can only view history for their authorized tasks + */ + getTaskRunHistory( + taskRunId: TaskRunIdValue, + actingAgentId: AgentIdValue, + options: { + limit?: number; + startDate?: Date; + endDate?: Date; + status?: TaskRunTerminalStatusEnum; + } = {}, + ): TaskRunHistoryEntry[] { + this.logger.trace("Getting task history", { + taskRunId, + agentId: actingAgentId, + options, + }); + this.ac.checkPermission(taskRunId, actingAgentId, READ_ONLY_ACCESS); + + const taskRun = this.getTaskRun(taskRunId, actingAgentId); + let history = taskRun.history; + + // Apply filters + if (options.startDate) { + history = history.filter((entry) => entry.timestamp >= options.startDate!); + } + if (options.endDate) { + history = history.filter((entry) => entry.timestamp <= options.endDate!); + } + if (options.status) { + history = history.filter((entry) => entry.terminalStatus === status); + } + if (options.limit) { + history = history.slice(-options.limit); + } + + return history; + } + + destroy() { + this.logger.debug("Destroy"); + if (this.taskStartIntervalId) { + clearInterval(this.taskStartIntervalId); + this.taskStartIntervalId = null; + } + } +} diff --git a/src/tasks/state/builder.ts b/src/tasks/state/builder.ts new file mode 100644 index 0000000..72cfeef --- /dev/null +++ b/src/tasks/state/builder.ts @@ -0,0 +1,297 @@ +import { AgentKindEnum, AgentTypeValue } from "src/agents/registry/dto.js"; +import { BaseStateBuilder } from "src/base/state/base-state-builder.js"; +import { + AgentTypeRegisterEvent, + TaskConfigCreateEvent, + TaskConfigDestroyEvent, + TaskConfigUpdateEvent, + TaskHistoryEntryCreateEvent, + TaskPoolChangeEvent, + TaskRunCreateEvent, + TaskRunDestroyEvent, + TaskRunUpdateEvent, + TaskStateDataType, + TaskStateDataTypeSchema, +} from "./dto.js"; +import { stringToAgentType } from "src/agents/agent-id.js"; +import { + TaskConfigIdValue, + TaskConfig, + TaskTypeValue, + TaskConfigPoolStats, + TaskKindEnum, + TaskRunIdValue, + TaskRun, +} from "../manager/dto.js"; +import { stringToTaskConfig, stringToTaskType, taskSomeIdToKindValue } from "../task-id.js"; +import { clone } from "remeda"; +import { updateDeepPartialObject } from "src/utils/objects.js"; + +// Define update types as const to ensure type safety +export const StateUpdateType = { + AGENT_TYPE: "agent_type", + TASK_CONFIG: "task_config", + TASK_RUN: "task_config", + POOL: "pool", + HISTORY_ENTRY: "history_entry", + FULL: "full", +} as const; + +// Define the type for the update types +export type StateUpdateType = (typeof StateUpdateType)[keyof typeof StateUpdateType]; + +export interface TaskRunInfo { + taskRunId: string; + taskConfigId: string; + taskConfigVersion: number; + taskRun: TaskRun; + isDestroyed: boolean; +} + +export interface TaskPool { + taskType: TaskTypeValue; + poolStats: TaskConfigPoolStats; + versions: [number, TaskConfigPoolStats][]; +} + +export interface TaskState { + registeredAgentTypes: Map>; + taskConfigs: Map; + taskRunPools: Map>; + taskRuns: Map; +} + +export class TaskStateBuilder extends BaseStateBuilder { + constructor() { + super(TaskStateDataTypeSchema, { + registeredAgentTypes: new Map(), + taskConfigs: new Map(), + taskRunPools: new Map(), + taskRuns: new Map(), + }); + } + + protected processStateUpdate(data: TaskStateDataType): void { + switch (data.kind) { + case "agent_type_register": + this.handleAgentTypeRegister(data); + this.emit("state:updated", { + type: StateUpdateType.AGENT_TYPE, + ids: [data.agentTypeId], + }); + break; + case "task_config_create": + this.handleTaskConfigCreate(data); + this.emit("state:updated", { + type: StateUpdateType.TASK_CONFIG, + ids: [data.taskConfigId, data.taskType], + }); + break; + case "task_config_update": + this.handleTaskConfigUpdate(data); + this.emit("state:updated", { + type: StateUpdateType.TASK_CONFIG, + ids: [data.taskConfigId, data.taskType], + }); + break; + case "task_config_destroy": + this.handleTaskConfigDestroy(data); + this.emit("state:updated", { + type: StateUpdateType.TASK_CONFIG, + ids: [data.taskConfigId, data.taskType], + }); + break; + case "pool_change": + this.handlePoolChange(data); + this.emit("state:updated", { + type: StateUpdateType.POOL, + ids: [data.taskTypeId], + }); + break; + case "task_run_create": + case "task_run_update": + case "task_run_destroy": + this.handleTaskRunLifecycle(data); + this.emit("state:updated", { + type: StateUpdateType.TASK_RUN, + ids: [data.taskRunId], + }); + break; + case "history_entry_create": + this.handleHistoryEntryCreate(data); + this.emit("state:updated", { + type: StateUpdateType.HISTORY_ENTRY, + ids: [data.taskRunId], + }); + break; + } + } + handleHistoryEntryCreate(data: TaskHistoryEntryCreateEvent) { + const { taskRunId, entry } = data; + const taskRun = this.state.taskRuns.get(taskRunId); + if (!taskRun) { + throw new Error(`Task run ${taskRunId} was not found`); + } + + taskRun.taskRun.history.push(entry); + } + + private handleTaskRunLifecycle( + data: TaskRunCreateEvent | TaskRunUpdateEvent | TaskRunDestroyEvent, + ): void { + const { taskRunId } = data; + + switch (data.kind) { + case "task_run_create": { + if (this.state.taskRuns.has(taskRunId)) { + throw new Error(`Task run ${taskRunId} already exists`); + } + + const taskConfigId = stringToTaskConfig(data.taskConfigId); + + this.state.taskRuns.set(taskRunId, { + taskRunId, + taskConfigId: data.taskConfigId, + taskConfigVersion: taskConfigId.taskConfigVersion, + taskRun: clone(data.taskRun), + isDestroyed: false, + }); + break; + } + case "task_run_update": { + const taskRunInfo = this.state.taskRuns.get(taskRunId); + if (!taskRunInfo) { + throw new Error(`Task run info ${taskRunId} doesn't exist for update`); + } + + updateDeepPartialObject(taskRunInfo.taskRun, data.taskRun); + break; + } + case "task_run_destroy": { + const taskRunInfo = this.state.taskRuns.get(taskRunId); + if (!taskRunInfo) { + throw new Error(`Task run info ${taskRunId} doesn't exist for destroy`); + } + taskRunInfo.isDestroyed = true; + break; + } + } + } + + handlePoolChange(data: TaskPoolChangeEvent) { + const taskTypeId = stringToTaskType(data.taskTypeId); + const pool = this.state.taskRunPools.get(taskSomeIdToKindValue(taskTypeId) as TaskKindEnum); + if (!pool) { + throw new Error(`Missing pool for type: ${data.taskTypeId}`); + } + + const poolType = pool.get(taskTypeId.taskType); + if (!poolType) { + throw new Error(`Missing pool type: ${data.taskTypeId}`); + } + + poolType.poolStats = data.poolStats; + poolType.versions = data.versions; + } + + handleTaskConfigDestroy(data: TaskConfigDestroyEvent) { + const { taskConfigId: taskConfigIdStr, taskType } = data; + + // Remove the config + if (!this.state.taskConfigs.has(taskConfigIdStr)) { + throw new Error(`Task config not found for task pool type: ${taskType}`); + } + this.state.taskConfigs.delete(taskConfigIdStr); + const taskConfigId = stringToTaskConfig(taskConfigIdStr); + + // Clean up related pool + const taskKindPool = this.state.taskRunPools.get(taskConfigId.taskKind); + if (taskKindPool) { + taskKindPool.delete(taskConfigId.taskType); + if (taskKindPool.size === 0) { + this.state.taskRunPools.delete(taskConfigId.taskKind); + } + } + } + handleTaskConfigUpdate(data: TaskConfigUpdateEvent) { + const { config, taskConfigId, taskType: taskTypeId } = data; + + // Update existing config + const taskConfigsVersions = this.state.taskConfigs.get(taskConfigId); + if (!taskConfigsVersions) { + throw new Error(`Task config versions not found for task type: ${taskTypeId}`); + } + taskConfigsVersions.push(config); + } + handleTaskConfigCreate(data: TaskConfigCreateEvent) { + const { config, taskType } = data; + + // Store the config + this.state.taskConfigs.set(taskType, [config]); + + // Initialize the pool if needed + let pool = this.state.taskRunPools.get(config.taskKind); + if (!pool) { + pool = new Map(); + this.state.taskRunPools.set(config.taskKind, pool); + } + + const poolType: TaskPool = { + taskType, + poolStats: { + poolSize: 0, + active: 0, + terminated: 0, + completed: 0, + running: 0, + failed: 0, + stopped: 0, + waiting: 0, + created: 0, + total: 0, + }, + versions: [], + }; + + pool.set(config.taskType, poolType); + } + handleAgentTypeRegister(data: AgentTypeRegisterEvent) { + const agentTypeId = stringToAgentType(data.agentTypeId); + let kindSet = this.state.registeredAgentTypes.get(agentTypeId.agentKind); + if (!kindSet) { + kindSet = new Set(); + this.state.registeredAgentTypes.set(agentTypeId.agentKind, kindSet); + } + kindSet.add(agentTypeId.agentType); + } + protected reset(): void { + this.state.registeredAgentTypes.clear(); + this.state.taskConfigs.clear(); + this.state.taskRunPools.clear(); + this.state.taskRuns.clear(); + } + + getAllTaskRuns(): TaskRunInfo[] { + return Array.from(this.state.taskRuns.values()); + } + + getTaskConfig(taskTypeId: string, taskConfigVersion?: number): TaskConfig | undefined { + const versions = this.state.taskConfigs.get(taskTypeId); + if (!versions) { + throw new Error(`Task config versions not found for '${taskTypeId}'`); + } + if (taskConfigVersion != null) { + return versions.find((v) => v.taskConfigVersion === taskConfigVersion); + } + return versions.at(-1); + } + + private getTaskPoolsMap(agentKindId: TaskKindEnum): Map | undefined { + return this.state.taskRunPools.get(agentKindId); + } + + getTaskPool(agentKind: TaskKindEnum, agentType: string): TaskPool | undefined { + const map = this.getTaskPoolsMap(agentKind); + return map?.get(agentType); + } +} diff --git a/src/tasks/state/dto.ts b/src/tasks/state/dto.ts new file mode 100644 index 0000000..e2c5839 --- /dev/null +++ b/src/tasks/state/dto.ts @@ -0,0 +1,115 @@ +import { z } from "zod"; +import { + TaskConfigIdValueSchema, + TaskConfigPoolStatsSchema, + TaskConfigSchema, + TaskRunHistoryEntrySchema, + TaskRunIdValueSchema, + TaskRunSchema, + TaskTypeValueSchema, +} from "../manager/dto.js"; + +// Base schemas +export const TaskEventKindEnum = z.enum([ + "agent_type_register", + "task_config_create", + "task_config_update", + "task_config_destroy", + "pool_change", + "task_run_create", + "task_run_update", + "task_run_destroy", + "history_entry_create", +]); + +export const BaseTaskEventSchema = z.object({ + kind: TaskEventKindEnum, +}); + +// Agent Types Events +export const AgentTypeRegisterEventSchema = BaseTaskEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.agent_type_register), + agentTypeId: z.string(), +}); +export type AgentTypeRegisterEvent = z.infer; + +// Task Config Events + +export const BaseTaskConfigLifecycleEventSchema = BaseTaskEventSchema.extend({ + taskConfigId: TaskConfigIdValueSchema, + taskType: TaskTypeValueSchema, +}); +export type BaseTaskConfigLifecycleEvent = z.infer; + +export const TaskConfigCreateEventSchema = BaseTaskConfigLifecycleEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.task_config_create), + config: TaskConfigSchema, +}); +export type TaskConfigCreateEvent = z.infer; + +export const TaskConfigUpdateEventSchema = BaseTaskConfigLifecycleEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.task_config_update), + config: TaskConfigSchema, +}); +export type TaskConfigUpdateEvent = z.infer; + +export const TaskConfigDestroyEventSchema = BaseTaskConfigLifecycleEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.task_config_destroy), +}); +export type TaskConfigDestroyEvent = z.infer; + +// Task Status Lifecycle Events + +export const BaseTaskRunLifecycleEventSchema = BaseTaskEventSchema.extend({ + taskRunId: TaskRunIdValueSchema, +}); +export type BaseTaskRunLifecycleEvent = z.infer; + +export const TaskRunCreateEventSchema = BaseTaskRunLifecycleEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.task_run_create), + taskConfigId: TaskConfigIdValueSchema, + taskRun: TaskRunSchema, +}); +export type TaskRunCreateEvent = z.infer; + +export const TaskRunUpdateEventSchema = BaseTaskRunLifecycleEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.task_run_update), + taskRun: TaskRunSchema.omit({ history: true }).partial(), +}); +export type TaskRunUpdateEvent = z.infer; + +export const TaskRunDestroyEventSchema = BaseTaskRunLifecycleEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.task_run_destroy), +}); +export type TaskRunDestroyEvent = z.infer; + +// Task History Entry +export const TaskHistoryEntryCreateEventSchema = BaseTaskEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.history_entry_create), + taskRunId: TaskRunIdValueSchema, + entry: TaskRunHistoryEntrySchema, +}); +export type TaskHistoryEntryCreateEvent = z.infer; + +// Pool Events +export const TaskPoolChangeEventSchema = BaseTaskEventSchema.extend({ + kind: z.literal(TaskEventKindEnum.enum.pool_change), + taskTypeId: z.string(), + poolStats: TaskConfigPoolStatsSchema, + versions: z.array(z.tuple([z.number(), TaskConfigPoolStatsSchema])), +}); +export type TaskPoolChangeEvent = z.infer; + +// Union of all event types +export const TaskStateDataTypeSchema = z.discriminatedUnion("kind", [ + AgentTypeRegisterEventSchema, + TaskConfigCreateEventSchema, + TaskConfigUpdateEventSchema, + TaskConfigDestroyEventSchema, + TaskRunCreateEventSchema, + TaskRunUpdateEventSchema, + TaskRunDestroyEventSchema, + TaskHistoryEntryCreateEventSchema, + TaskPoolChangeEventSchema, +]); +export type TaskStateDataType = z.infer; diff --git a/src/tasks/state/logger.ts b/src/tasks/state/logger.ts new file mode 100644 index 0000000..4998c34 --- /dev/null +++ b/src/tasks/state/logger.ts @@ -0,0 +1,114 @@ +import { BaseStateLogger } from "../../base/state/base-state-logger.js"; +import { + AgentTypeRegisterEvent, + TaskConfigCreateEvent, + TaskConfigDestroyEvent, + TaskConfigUpdateEvent, + TaskEventKindEnum, + TaskHistoryEntryCreateEvent, + TaskPoolChangeEvent, + TaskRunCreateEvent, + TaskRunDestroyEvent, + TaskRunUpdateEvent, + TaskStateDataTypeSchema, +} from "./dto.js"; + +export const DEFAULT_NAME = "task_state"; +export const DEFAULT_PATH = ["logs"] as readonly string[]; + +class TaskStateLogger extends BaseStateLogger { + constructor(logPath?: string) { + super(DEFAULT_PATH, DEFAULT_NAME, logPath); + } + + public logAgentTypeRegister(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.agent_type_register, + ...data, + }, + }); + } + + public logTaskConfigCreate(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.task_config_create, + ...data, + }, + }); + } + + public logTaskConfigUpdate(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.task_config_update, + ...data, + }, + }); + } + + public logTaskConfigDestroy(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.task_config_destroy, + ...data, + }, + }); + } + + public logTaskRunCreate(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.task_run_create, + ...data, + }, + }); + } + + public logTaskRunUpdate(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.task_run_update, + ...data, + }, + }); + } + + public logTaskRunDestroy(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.task_run_destroy, + ...data, + }, + }); + } + + public logTaskHistoryEntryCreate(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.history_entry_create, + ...data, + }, + }); + } + + public logPoolChange(data: Omit) { + this.logUpdate({ + data: { + kind: TaskEventKindEnum.Values.pool_change, + ...data, + }, + }); + } +} + +let instance: TaskStateLogger | null = null; + +// Export singleton instance +export const taskStateLogger = () => { + if (!instance) { + instance = new TaskStateLogger(); + } + return instance; +}; diff --git a/src/tasks/task-id.ts b/src/tasks/task-id.ts new file mode 100644 index 0000000..4099cec --- /dev/null +++ b/src/tasks/task-id.ts @@ -0,0 +1,158 @@ +import { + EntityKindId, + EntityNumId, + entityToKindString, + entityToTypeIdString, + entityToVersionIdString, + entityToVersionNumIdString, + EntityTypeId, + EntityVersionId, + EntityVersionNumId, + stringToEntityKind, + stringToEntityType, + stringToEntityVersion, + stringToEntityVersionNum, +} from "src/base/entity-id.js"; +import { + TaskConfigIdValue, + TaskKindEnum, + TaskKindEnumSchema, + TaskKindValue, + TaskRunIdValue, + TaskTypeValue, +} from "./manager/dto.js"; + +// Task specific interfaces with domain-specific naming +export interface TaskKindId { + taskKind: TaskKindEnum; +} + +export interface TaskTypeId extends TaskKindId { + taskType: string; +} + +export interface TaskConfigId extends TaskTypeId { + taskConfigVersion: number; +} + +export interface TaskRunId extends TaskConfigId { + taskRunNum: number; +} + +// Public conversion functions to generic types +export function taskKindToEntityKindId(taskKindId: TaskKindId): EntityKindId { + return { + kind: taskKindId.taskKind, + }; +} + +export function taskTypeToEntityTypeId(taskTypeId: TaskTypeId): EntityTypeId { + return { + ...taskKindToEntityKindId(taskTypeId), + type: taskTypeId.taskType, + }; +} + +export function taskConfigToEntityVersionId( + taskConfigId: TaskConfigId, +): EntityVersionId { + return { + ...taskTypeToEntityTypeId(taskConfigId), + version: taskConfigId.taskConfigVersion, + }; +} + +export function taskRunToEntityNumId(taskRunId: TaskRunId): EntityNumId { + return { + ...taskTypeToEntityTypeId(taskRunId), + num: taskRunId.taskRunNum, + }; +} + +export function taskRunToEntityVersionNumId( + taskRunId: TaskRunId, +): EntityVersionNumId { + return { + ...taskRunToEntityNumId(taskRunId), + version: taskRunId.taskConfigVersion, + }; +} + +// Task ID validation +function validateTaskKind(kind: string): TaskKindEnum { + const result = TaskKindEnumSchema.safeParse(kind); + if (!result.success) { + throw new Error(`Invalid task kind: ${kind}`); + } + return result.data; +} + +// Task conversion functions from string +export function stringToTaskKind(str: string): TaskKindId { + const generic = stringToEntityKind(str, validateTaskKind); + return { + taskKind: generic.kind, + }; +} + +export function stringToTaskType(str: string): TaskTypeId { + const generic = stringToEntityType(str, validateTaskKind); + return { + taskKind: generic.kind, + taskType: generic.type, + }; +} + +export function stringToTaskConfig(str: string): TaskConfigId { + const generic = stringToEntityVersion(str, validateTaskKind); + return { + taskKind: generic.kind, + taskType: generic.type, + taskConfigVersion: generic.version, + }; +} + +export function stringToTaskRun(str: string): TaskRunId { + const generic = stringToEntityVersionNum(str, validateTaskKind); + return { + taskKind: generic.kind, + taskType: generic.type, + taskRunNum: generic.num, + taskConfigVersion: generic.version, + }; +} + +// String conversion functions +export function taskSomeIdToKindValue( + task: TaskKindId | TaskTypeId | TaskConfigId | TaskRunId, +): TaskKindValue { + return entityToKindString(taskKindToEntityKindId(task)); +} + +export function taskSomeIdToTypeValue(task: TaskTypeId | TaskConfigId | TaskRunId): TaskTypeValue { + return entityToTypeIdString(taskTypeToEntityTypeId(task)); +} + +export function taskConfigIdToValue(taskConfigId: TaskConfigId): TaskConfigIdValue { + return entityToVersionIdString(taskConfigToEntityVersionId(taskConfigId)); +} + +export function taskRunIdToString(taskId: TaskRunId): TaskRunIdValue { + return entityToVersionNumIdString(taskRunToEntityVersionNumId(taskId)); +} + +// Generic conversion that handles any task ID type +export function taskSomeIdToString( + taskSomeId: TaskKindId | TaskTypeId | TaskConfigId | TaskRunId, +): string { + if ("num" in taskSomeId) { + return taskRunIdToString(taskSomeId as TaskRunId); + } + if ("version" in taskSomeId) { + return taskConfigIdToValue(taskSomeId as TaskConfigId); + } + if ("agentType" in taskSomeId) { + return taskSomeIdToTypeValue(taskSomeId as TaskTypeId); + } + return taskSomeIdToKindValue(taskSomeId); +} diff --git a/src/tasks/task-manager.ts b/src/tasks/task-manager.ts deleted file mode 100644 index d77a39e..0000000 --- a/src/tasks/task-manager.ts +++ /dev/null @@ -1,797 +0,0 @@ -import { FrameworkError } from "bee-agent-framework"; -import { Logger } from "bee-agent-framework/logger/logger"; -import { AgentKind, AgentKindSchema } from "src/agents/agent-registry.js"; -import { getTaskStateLogger } from "src/tasks/task-state-logger.js"; -import { updateDeepPartialObject } from "src/utils/objects.js"; -import { z } from "zod"; - -export const TaskConfigSchema = z - .object({ - id: z.string().describe("Unique identifier for the task"), - input: z.string().describe("Input data for the task."), - description: z.string().describe("Detail information about the task and its context."), - intervalMs: z.number().describe("Interval between task executions in milliseconds"), - runImmediately: z.boolean().describe("Whether to run the task immediately upon starting"), - maxRetries: z - .number() - .describe( - "Maximum number of retry attempts if task execution fails. undefined if no retries.", - ) - .nullish(), - retryDelayMs: z.number().describe("Delay between retry attempts in milliseconds").nullish(), - ownerAgentId: z.string().describe("Identifier of who owns/manages this task"), - agentKind: AgentKindSchema, - agentType: z.string().describe("Agent type that is allowed to execute this task"), - maxRuns: z - .number() - .describe("Maximum number of times this task should execute. undefined if infinite runs.") - .nullish(), - }) - .describe("Represents a periodic task configuration."); - -export type TaskConfig = z.infer; - -export const TaskTerminalStatusEnumSchema = z.enum(["STOPPED", "FAILED", "COMPLETED"]); -export type TaskTerminalStatusEnum = z.infer; - -export const TaskHistoryEntrySchema = z - .object({ - timestamp: z.date().describe("When this task execution occurred"), - terminalStatus: TaskTerminalStatusEnumSchema, - output: z.unknown().describe("Output produced by the task callback"), - error: z.string().optional().describe("Error message if execution failed"), - runNumber: z.number().describe("Which run number this was (1-based)"), - maxRuns: z - .number() - .describe("Maximum number of times this task should execute. Undefined means infinite runs.") - .nullish(), - retryAttempt: z.number().describe("How many retries were needed for this execution"), - maxRetries: z - .number() - .describe( - "Maximum number of retry attempts if task execution fails. undefined if no retries.", - ) - .nullish(), - agentId: z.string().nullable().describe("ID of agent that executed the task, if occupied"), - executionTimeMs: z.number().describe("How long the task execution took in milliseconds"), - }) - .describe("Records details about a single execution of a task"); - -export type TaskHistoryEntry = z.infer; - -export const TaskStatusEnumSchema = z.enum([ - "SCHEDULED", - "RUNNING", - "WAITING", - "STOPPED", - "FAILED", - "COMPLETED", - "REMOVED", -]); -export type TaskStatusEnum = z.infer; - -// Update existing TaskStatus schema to include history -export const TaskStatusSchema = z - .object({ - id: z - .string() - .min(1, "Task ID cannot be empty") - .describe("Unique identifier matching the corresponding AgentTask"), - status: TaskStatusEnumSchema.describe("The status of the task."), - isOccupied: z - .boolean() - .describe("Indicates if the task is currently being operated on by an agent"), - occupiedSince: z - .date() - .optional() - .nullable() - .describe("Timestamp when the task was marked as occupied. undefined if not occupied"), - startRunAt: z.date().optional().describe("Timestamp of the execution start."), - lastRunAt: z.date().optional().describe("Timestamp of the last successful execution"), - nextRunAt: z.date().optional().describe("Expected timestamp of the next scheduled execution"), - errorCount: z.number().int().describe("Count of consecutive execution failures"), - currentRetryAttempt: z - .number() - .describe("Current retry count. Maximum retries configured via maxRetries"), - ownerAgentId: z.string().describe("ID of the agent who owns/manages this task"), - currentAgentId: z - .string() - .optional() - .nullable() - .describe("ID of the agent currently operating on the task. undefined if not occupied"), - completedRuns: z - .number() - .int() - .describe("Number of times this task has been successfully executed"), - history: z.array(TaskHistoryEntrySchema).describe("History of task executions"), - maxHistoryEntries: z - .number() - .optional() - .describe("Maximum number of history entries to keep. Undefined means keep all history."), - }) - .describe("Represents the current status and execution state of a task"); - -export type TaskStatus = z.infer; - -export const TaskSchema = z - .object({ - id: z.string().describe("Unique identifier for the task"), - status: TaskStatusSchema, - config: TaskConfigSchema, - }) - .describe("Represents a periodic task configuration."); -export type Task = z.infer; - -export class PermissionError extends Error { - constructor(message: string) { - super(message); - this.name = "PermissionError"; - } -} - -export class TaskManager { - private readonly logger: Logger; - private tasks = new Map< - string, - Task & { - intervalId: NodeJS.Timeout | null; - } - >(); - private removedTasks: Task[] = []; - private scheduledTasksToStart: { taskId: string; agentId: string }[] = []; - private taskStartIntervalId: NodeJS.Timeout | null = null; - private registeredAgentTypes = new Map(); - - constructor( - private onTaskStart: ( - task: TaskConfig, - taskManager: TaskManager, - callbacks: { - onAgentCreate: (taskId: string, agentId: string, taskManage: TaskManager) => void; - onAgentComplete: ( - output: string, - taskId: string, - agentId: string, - taskManage: TaskManager, - ) => void; - onAgentError: ( - err: Error, - taskId: string, - agentId: string, - taskManage: TaskManager, - ) => void; - }, - ) => Promise, - private options: { - errorHandler?: (error: Error, taskId: string) => void; - occupancyTimeoutMs?: number; - adminIds?: string[]; - maxHistoryEntries?: number; - } = {}, - ) { - this.logger = Logger.root.child({ name: "TaskManager" }); - this.logger.info("Initializing TaskManager"); - - this.options = { - errorHandler: (error: Error, taskId: string) => { - this.logger.error("Task error occurred", { taskId, error }); - }, - occupancyTimeoutMs: 30 * 60 * 1000, - adminIds: [], - maxHistoryEntries: 100, // Default to keeping last 100 entries - ...options, - }; - - this.taskStartIntervalId = setInterval(async () => { - try { - await this.processNextStartTask(); // Your async function - } catch (err) { - this.logger.error("Process next start task error", err); - } - }, 100); // Runs every 100ms (0.1 second) - } - - registerAgentType(agentKind: AgentKind, agentType: string): void { - let types = this.registeredAgentTypes.get(agentKind); - if (!types) { - types = [agentType]; - this.registeredAgentTypes.set(agentKind, types); - } else { - types.push(agentType); - } - } - - /** - * Add a history entry for a task - * @private - */ - private addHistoryEntry(taskId: string, entry: TaskHistoryEntry): void { - const task = this.tasks.get(taskId); - if (!task) { - return; - } - - task.status.history.push(entry); - getTaskStateLogger().logHistoryEntry(taskId, entry); - - // Trim history if it exceeds maximum entries - const maxEntries = task.status.maxHistoryEntries ?? this.options.maxHistoryEntries; - if (maxEntries && task.status.history.length > maxEntries) { - task.status.history = task.status.history.slice(-maxEntries); - } - } - - /** - * Gets task history entries - * Agents can only view history for their authorized tasks - */ - getTaskHistory( - taskId: string, - agentId: string, - options: { - limit?: number; - startDate?: Date; - endDate?: Date; - status?: TaskTerminalStatusEnum; - } = {}, - ): TaskHistoryEntry[] { - this.logger.trace("Getting task history", { taskId, agentId, options }); - - if (!this.hasAgentPermission(taskId, agentId)) { - this.logger.error("Permission denied for viewing task history", { taskId, agentId }); - throw new PermissionError(`Agent ${agentId} does not have permission to view task ${taskId}`); - } - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.error("Task not found", { taskId }); - throw new Error(`Task ${taskId} not found`); - } - - let history = task.status.history; - - // Apply filters - if (options.startDate) { - history = history.filter((entry) => entry.timestamp >= options.startDate!); - } - if (options.endDate) { - history = history.filter((entry) => entry.timestamp <= options.endDate!); - } - if (options.status) { - history = history.filter((entry) => entry.terminalStatus === status); - } - if (options.limit) { - history = history.slice(-options.limit); - } - - return history; - } - - /** - * Checks if an agent has owner-level permissions for a task - */ - private hasOwnerPermission(taskId: string, agentId: string): boolean { - this.logger.trace("Checking owner permission", { taskId, agentId }); - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.debug("Task not found for permission check", { taskId }); - return false; - } - - const hasPermission = - task.config.ownerAgentId === agentId || this.options.adminIds?.includes(agentId) || false; - this.logger.debug("Owner permission check result", { taskId, agentId, hasPermission }); - return hasPermission; - } - - /** - * Checks if an agent has execution permissions for a task - */ - private hasAgentPermission(taskId: string, agentId: string): boolean { - this.logger.trace("Checking agent permission", { taskId, agentId }); - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.debug("Task not found for permission check", { taskId }); - return false; - } - - const hasPermission = - agentId.includes(`:${task.config.agentType}[`) || this.hasOwnerPermission(taskId, agentId); - this.logger.debug("Agent permission check result", { taskId, agentId, hasPermission }); - return hasPermission; - } - - /** - * Schedules a new task. - * Only owners and admins can schedule tasks. - */ - scheduleTask(task: TaskConfig, agentId: string): void { - this.logger.info("Scheduling new task", { taskId: task.id, agentId }); - - if (task.ownerAgentId !== agentId && !this.options.adminIds?.includes(agentId)) { - this.logger.error("Permission denied for task scheduling", { - taskId: task.id, - agentId, - ownerAgentId: task.ownerAgentId, - }); - throw new PermissionError( - `Agent ${agentId} cannot create task with owner ${task.ownerAgentId}`, - ); - } - - const types = this.registeredAgentTypes.get(task.agentKind); - if (!types || !types.includes(task.agentType)) { - throw new Error( - `Unregistered agent type for task.agentKind:${task.agentKind} task.agentType: ${task.agentType}`, - ); - } - - if (this.tasks.has(task.id)) { - this.logger.error("Task already exists", { taskId: task.id }); - throw new Error(`Task with id ${task.id} already exists`); - } - - const status: TaskStatus = { - id: task.id, - status: "SCHEDULED", - currentRetryAttempt: 0, - isOccupied: false, - errorCount: 0, - ownerAgentId: task.ownerAgentId, - completedRuns: 0, - history: [], - }; - - getTaskStateLogger().logStatusChange(status.id, status); - - this.tasks.set(task.id, { - id: task.id, - intervalId: null, - status, - config: task, - }); - - getTaskStateLogger().logConfigCreate(task.id, task); - - this.logger.info("Task scheduled successfully", { taskId: task.id }); - } - - /** - * Schedule task to start as soon as possible. - * Only owners and admins can start/stop tasks. - */ - scheduleTaskStart(taskId: string, agentId: string): void { - this.logger.info("Schedule task start", { taskId, agentId }); - this.scheduledTasksToStart.push({ taskId, agentId }); - } - - async processNextStartTask() { - if (!this.scheduledTasksToStart.length) { - return; - } - const { taskId, agentId } = this.scheduledTasksToStart.shift()!; - - this.logger.info("Starting task", { taskId, agentId }); - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.error("Task not found", { taskId }); - throw new Error(`Task ${taskId} not found`); - } - const { status } = task; - - if (!this.hasOwnerPermission(taskId, agentId)) { - this.logger.error("Permission denied for starting task", { taskId, agentId }); - throw new PermissionError( - `Agent ${agentId} does not have permission to start task ${taskId}`, - ); - } - - if (status.status === "RUNNING") { - this.logger.warn("Task already running", { taskId }); - throw new Error(`Task ${taskId} is already running`); - } - - this._updateTaskStatus(taskId, status, { - status: "RUNNING", - nextRunAt: new Date(Date.now() + task.config.intervalMs), - }); - - if (task.config.runImmediately) { - this.logger.debug("Executing task immediately", { taskId }); - await this.executeTask(taskId); - } - - if (!task.config.maxRuns || 1 < task.config.maxRuns) { - this.logger.debug("Setting up task interval", { taskId, intervalMs: task.config.intervalMs }); - const self = this; - task.intervalId = setInterval(async () => { - await self.executeTask(taskId); - }, task.config.intervalMs); - - status.status = "WAITING"; - - this._updateTaskStatus(taskId, status, { - status: "WAITING", - }); - } - - this.logger.info("Task started successfully", { taskId }); - } - - /** - * Stops a task. - * Only owners and admins can start/stop tasks. - */ - stopTask(taskId: string, agentId: string, isCompleted = false): void { - this.logger.info("Stopping task", { taskId, agentId }); - - if (!this.hasOwnerPermission(taskId, agentId)) { - this.logger.error("Permission denied for stopping task", { taskId, agentId }); - throw new PermissionError(`Agent ${agentId} does not have permission to stop task ${taskId}`); - } - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.error("Task not found", { taskId }); - throw new Error(`Task ${taskId} not found`); - } - const { status } = task; - - if (status.status === "STOPPED") { - this.logger.debug("Task already stopped", { taskId }); - return; - } - - if (task.intervalId) { - this.logger.debug("Clearing task interval", { taskId }); - clearInterval(task.intervalId); - task.intervalId = null; - } - - if (status.isOccupied) { - this.logger.debug("Releasing task occupancy before stop", { taskId }); - this.releaseTaskOccupancy(taskId, agentId); - } - - this._updateTaskStatus(taskId, status, { - status: isCompleted ? "COMPLETED" : "STOPPED", - nextRunAt: undefined, - }); - this.logger.info("Task stopped successfully", { taskId }); - } - - /** - * Removes a task completely. - * Only owners and admins can remove tasks. - */ - removeTask(taskId: string, agentId: string): void { - this.logger.info("Removing task", { taskId, agentId }); - - if (!this.hasOwnerPermission(taskId, agentId)) { - this.logger.error("Permission denied for removing task", { taskId, agentId }); - throw new PermissionError( - `Agent ${agentId} does not have permission to remove task ${taskId}`, - ); - } - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.error("Task not found", { taskId }); - throw new Error(`Task ${taskId} not found`); - } - const { status } = task; - - if (status.status === "RUNNING") { - this.logger.debug("Stopping running task before removal", { taskId }); - this.stopTask(taskId, agentId); - } - - if (status.isOccupied) { - this.logger.debug("Releasing task occupancy before removal", { taskId }); - this.releaseTaskOccupancy(taskId, agentId); - } - - this._updateTaskStatus(taskId, status, { status: "REMOVED" }); - this.tasks.delete(taskId); - this.removedTasks.push(task); - this.logger.info("Task removed successfully", { taskId }); - } - - /** - * Sets task as occupied. - * Only authorized agents can occupy tasks. - */ - setTaskOccupied(taskId: string, agentId: string): boolean { - this.logger.info("Setting task as occupied", { taskId, agentId }); - - if (!this.hasAgentPermission(taskId, agentId)) { - this.logger.error("Permission denied for occupying task", { taskId, agentId }); - throw new PermissionError(`Agent ${agentId} is not authorized to operate on task ${taskId}`); - } - - const task = this.tasks.get(taskId); - if (!task || task.status.isOccupied) { - this.logger.debug("Task not available for occupancy", { taskId, exists: !!task }); - return false; - } - const { status } = task; - - this._updateTaskStatus(taskId, status, { - isOccupied: true, - occupiedSince: new Date(), - currentAgentId: agentId, - }); - - if (this.options.occupancyTimeoutMs) { - this.logger.debug("Setting occupancy timeout", { - taskId, - timeoutMs: this.options.occupancyTimeoutMs, - }); - setTimeout(() => { - this.releaseTaskOccupancy(taskId, agentId); - }, this.options.occupancyTimeoutMs); - } - - this.logger.info("Task occupied successfully", { taskId, agentId }); - return true; - } - - /** - * Releases task occupancy. - * Only the current agent or owners can release occupancy. - */ - releaseTaskOccupancy(taskId: string, agentId: string): boolean { - this.logger.info("Releasing task occupancy", { taskId, agentId }); - - const task = this.tasks.get(taskId); - if (!task || !task.status.isOccupied) { - this.logger.debug("Task not available for release", { taskId, exists: !!task }); - return false; - } - const { status } = task; - - if (status.currentAgentId !== agentId && !this.hasOwnerPermission(taskId, agentId)) { - this.logger.error("Permission denied for releasing task occupancy", { taskId, agentId }); - throw new PermissionError(`Agent ${agentId} cannot release occupancy of task ${taskId}`); - } - - this._updateTaskStatus(taskId, status, { - isOccupied: false, - occupiedSince: null, - currentAgentId: null, - }); - - this.logger.info("Task occupancy released successfully", { taskId }); - return true; - } - - /** - * Gets task status. - * Agents can only view their authorized tasks. - */ - getTaskStatus(taskId: string, agentId: string): TaskStatus { - this.logger.trace("Getting task status", { taskId, agentId }); - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.error("Task not found", { taskId }); - throw new Error(`Undefined taskId: ${taskId}`); - } - - if (!this.hasAgentPermission(taskId, agentId)) { - this.logger.error("Permission denied for viewing task status", { taskId, agentId }); - throw new PermissionError(`Agent ${agentId} does not have permission to view task ${taskId}`); - } - - return task.status; - } - - /** - * Update task status - */ - updateTaskStatus( - taskId: string, - agentId: string, - update: Partial>, - ) { - this.logger.trace("Updating task status", { taskId, agentId, update }); - const status = this.getTaskStatus(taskId, agentId); - return this._updateTaskStatus(taskId, status, update); - } - - // Just for auditlog - private _updateTaskStatus( - taskId: string, - status: TaskStatus, - update: Partial, - ): TaskStatus { - updateDeepPartialObject(status, update); - getTaskStateLogger().logStatusChange(taskId, update); - return status; - } - - /** - * Gets all task statuses visible to the agent. - */ - getAllTaskStatuses(agentId: string): TaskStatus[] { - this.logger.trace("Getting all task statuses", { agentId }); - - const statuses = Array.from(this.tasks.values()) - .filter((task) => this.hasAgentPermission(task.status.id, agentId)) - .map((task) => task.status); - - this.logger.debug("Retrieved task statuses", { agentId, count: statuses.length }); - return statuses; - } - - /** - * Checks if a task is currently occupied. - */ - isTaskOccupied(taskId: string, agentId: string): boolean { - this.logger.trace("Checking task occupancy", { taskId, agentId }); - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.error("Task not found", { taskId }); - throw new Error(`Undefined taskId: ${taskId}`); - } - - if (!this.hasAgentPermission(taskId, agentId)) { - this.logger.error("Permission denied for checking task occupancy", { taskId, agentId }); - throw new PermissionError(`Agent ${agentId} does not have permission to view task ${taskId}`); - } - - return task.status.isOccupied; - } - - /** - * Executes a task with retry logic and records history. - * @private - */ - private async executeTask(taskId: string): Promise { - this.logger.debug("Executing task", { taskId }); - - const task = this.tasks.get(taskId); - if (!task) { - this.logger.warn("Task not found for execution", { taskId }); - return; - } - const { status } = task; - - const retryAttempt = status.currentRetryAttempt; - if (retryAttempt > 0) { - this.logger.debug("Retry attempt", { retryAttempt, maxRetries: task.config.maxRetries }); - if (!!task.config.maxRetries && retryAttempt >= task.config.maxRetries) { - this.logger.warn("Last retry attempt", { taskId }); - } - } - - if (status.status === "COMPLETED" || status.isOccupied) { - this.logger.debug("Skipping task execution", { - taskId, - reason: status.status === "COMPLETED" ? "completed" : "occupied", - }); - return; - } - - const startTime = Date.now(); - - this._updateTaskStatus(taskId, status, { - lastRunAt: new Date(), - nextRunAt: new Date(Date.now() + task.config.intervalMs), - }); - - this.logger.debug("Executing task callback", { - taskId, - lastRunAt: status.lastRunAt, - nextRunAt: status.nextRunAt, - }); - - await this.onTaskStart(task.config, this, { - onAgentCreate(taskId, agentId, taskManager) { - taskManager.setTaskOccupied(taskId, agentId); - }, - onAgentComplete(output, taskId, agentId, taskManager) { - const status = taskManager.getTaskStatus(taskId, agentId); - taskManager.updateTaskStatus(taskId, agentId, { - completedRuns: status.completedRuns + 1, - }); - - // Record history entry - taskManager.addHistoryEntry(taskId, { - timestamp: new Date(), - terminalStatus: "COMPLETED", - output, - runNumber: task.status.completedRuns, - maxRuns: task.config.maxRuns, - retryAttempt: status.currentRetryAttempt, - maxRetries: task.config.maxRetries, - agentId: task.status.currentAgentId!, - executionTimeMs: Date.now() - startTime, - }); - - taskManager.logger.debug("Task executed successfully", { - taskId, - completedRuns: task.status.completedRuns, - maxRuns: task.config.maxRuns, - }); - - taskManager.releaseTaskOccupancy(taskId, agentId); - // Check if we've reached maxRuns - if (task.config.maxRuns && task.status.completedRuns >= task.config.maxRuns) { - taskManager.stopTask(taskId, task.config.ownerAgentId); - taskManager.logger.info("Task reached maximum runs and has been stopped", { - taskId, - completedRuns: task.status.completedRuns, - maxRuns: task.config.maxRuns, - }); - } - }, - async onAgentError(err, taskId, agentId, taskManager) { - let error; - if (err instanceof FrameworkError) { - error = err.explain(); - } else { - error = err instanceof Error ? err.message : String(err); - } - - const status = taskManager.getTaskStatus(taskId, agentId); - taskManager.updateTaskStatus(taskId, agentId, { - errorCount: status.errorCount + 1, - completedRuns: task.status.completedRuns + 1, - }); - const retryAttempt = status.currentRetryAttempt; - - // Record history entry - taskManager.addHistoryEntry(taskId, { - timestamp: new Date(), - terminalStatus: "FAILED", - error, - runNumber: task.status.completedRuns, - maxRuns: task.config.maxRuns, - retryAttempt, - maxRetries: task.config.maxRetries, - agentId: task.status.currentAgentId!, - executionTimeMs: Date.now() - startTime, - }); - - taskManager.logger.error(`Task execution failed ${error}`, { - taskId, - runNumber: task.status.completedRuns, - maxRuns: task.config.maxRuns, - retryAttempt, - maxRetries: task.config.maxRetries, - errorCount: task.status.errorCount, - error, - }); - - if (taskManager.options.errorHandler) { - taskManager.options.errorHandler(err as Error, taskId); - } - - taskManager.logger.debug("Releasing task occupancy before removal", { taskId }); - taskManager.releaseTaskOccupancy(taskId, agentId); - if (task.config.maxRetries) { - if (retryAttempt >= task.config.maxRetries) { - taskManager.stopTask(taskId, task.config.ownerAgentId); - } else { - taskManager.updateTaskStatus(taskId, task.config.ownerAgentId, { - currentRetryAttempt: retryAttempt + 1, - }); - } - } - }, - }); - } - - destroy() { - this.logger.debug("Destroy"); - if (this.taskStartIntervalId) { - clearInterval(this.taskStartIntervalId); - this.taskStartIntervalId = null; - } - } -} diff --git a/src/tasks/task-state-logger.ts b/src/tasks/task-state-logger.ts deleted file mode 100644 index ca02037..0000000 --- a/src/tasks/task-state-logger.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { TaskConfig, TaskHistoryEntry, TaskStatus } from "src/tasks/task-manager.js"; -import { BaseAuditLog, LogUpdate } from "../base/audit-log.js"; - -export const DEFAULT_NAME = "task_state"; -export const DEFAULT_PATH = ["logs"] as readonly string[]; - -export enum TaskUpdateTypeEnum { - CONFIG = "config", - STATUS = "status", - HISTORY = "history", -} - -export interface TaskUpdate extends LogUpdate { - taskId: string; -} - -export type TaskStateData = TaskConfig | Partial | TaskHistoryEntry; - -class TaskStateLogger extends BaseAuditLog { - constructor(logPath?: string) { - super(DEFAULT_PATH, DEFAULT_NAME, logPath); - } - - public logConfigCreate(taskId: string, config: TaskConfig) { - this.logUpdate({ - type: TaskUpdateTypeEnum.CONFIG, - taskId, - data: config, - }); - } - - public logStatusChange(taskId: string, status: Partial) { - this.logUpdate({ - type: TaskUpdateTypeEnum.STATUS, - taskId, - data: status, - }); - } - - public logHistoryEntry(taskId: string, entry: TaskHistoryEntry) { - this.logUpdate({ - type: TaskUpdateTypeEnum.HISTORY, - taskId, - data: entry, - }); - } -} - -let instance: TaskStateLogger | null = null; - -// Export singleton instance -export const getTaskStateLogger = () => { - if (!instance) { - instance = new TaskStateLogger(); - } - return instance; -}; diff --git a/src/tasks/tool.ts b/src/tasks/tool.ts index 1c519c9..7c65b1f 100644 --- a/src/tasks/tool.ts +++ b/src/tasks/tool.ts @@ -7,7 +7,18 @@ import { ToolInput, } from "bee-agent-framework/tools/base"; import { z } from "zod"; -import { TaskConfigSchema, TaskHistoryEntry, TaskManager, TaskStatus } from "./task-manager.js"; +import { + ActingAgentIdValueSchema, + TaskConfig, + TaskConfigPoolStats, + TaskConfigSchema, + TaskKindEnumSchema, + TaskRun, + TaskRunHistoryEntry, + TaskRunIdValueSchema, + TaskTypeValueSchema, +} from "./manager/dto.js"; +import { TaskManager } from "./manager/manager.js"; export const TOOL_NAME = "task_runner"; @@ -18,9 +29,11 @@ export interface TaskManagerToolInput extends BaseToolOptions { export type TaskManagerToolResultData = | void | boolean - | TaskStatus - | TaskStatus[] - | TaskHistoryEntry[]; + | TaskConfig + | [TaskConfigPoolStats, [number, TaskConfigPoolStats][]] + | TaskRun + | TaskRun[] + | TaskRunHistoryEntry[]; export interface TaskManagerToolResult { method: string; @@ -28,68 +41,128 @@ export interface TaskManagerToolResult { data: TaskManagerToolResultData; } -export const ScheduleTaskSchema = z +export const CreateTaskConfigSchema = z + .object({ + method: z.literal("createTaskConfig"), + taskConfig: TaskConfigSchema.omit({ + taskConfigId: true, + taskConfigVersion: true, + ownerAgentId: true, + }), + actingAgentId: ActingAgentIdValueSchema, + }) + .describe("Creates a new task configuration."); + +export const GetTaskConfigSchema = z .object({ - method: z.literal("scheduleTask"), - task: TaskConfigSchema, - supervisorAgentId: z.string(), + method: z.literal("getTaskConfig"), + taskKind: z.literal(TaskKindEnumSchema.Enum.operator), + taskType: TaskTypeValueSchema, + actingAgentId: ActingAgentIdValueSchema, + }) + .describe("Get latest task configuration for specific task kind and type."); + +export const UpdateTaskConfigSchema = z + .object({ + method: z.literal("updateTaskConfig"), + taskKind: z.literal(TaskKindEnumSchema.Enum.operator), + taskType: TaskTypeValueSchema, + update: TaskConfigSchema.partial().pick({ + taskConfigInput: true, + description: true, + intervalMs: true, + runImmediately: true, + maxRepeats: true, + maxRetries: true, + retryDelayMs: true, + concurrencyMode: true, + }), + actingAgentId: ActingAgentIdValueSchema, + }) + .describe("Update an existing task configuration."); + +export const DestroyTaskConfigSchema = z + .object({ + method: z.literal("destroyTaskConfig"), + taskKind: z.literal(TaskKindEnumSchema.Enum.operator), + taskType: TaskTypeValueSchema, + actingAgentId: ActingAgentIdValueSchema, + }) + .describe("Destroy an existing task configuration with all related task runs."); + +export const GetPoolStatsSchema = z + .object({ + method: z.literal("getPoolStats"), + taskKind: z.literal(TaskKindEnumSchema.Enum.operator), + taskType: TaskTypeValueSchema, + actingAgentId: ActingAgentIdValueSchema, }) .describe( - "Creates a new task with specified configuration. Requires owner or admin permissions.", + "Get statistics about the task run's pool for a specific task configuration kind and type", ); -export const StartTaskSchema = z +export const CreateTaskRunSchema = z + .object({ + method: z.literal("createTaskRun"), + taskKind: z.literal(TaskKindEnumSchema.Enum.operator), + taskType: TaskTypeValueSchema, + taskRunInput: z.string().describe(`Task input specific for the run.`), + actingAgentId: ActingAgentIdValueSchema, + }) + .describe("Creates a new task run from task configuration."); + +export const ScheduleStartTaskRunSchema = z .object({ - method: z.literal("startTask"), - taskId: z.string(), - supervisorAgentId: z.string(), + method: z.literal("scheduleStartTaskRun"), + taskRunId: TaskRunIdValueSchema, + actingAgentId: ActingAgentIdValueSchema, }) - .describe("Starts periodic execution of a task. Requires owner or admin permissions."); + .describe("Starts a task run."); -export const StopTaskSchema = z +export const StopTaskRunSchema = z .object({ - method: z.literal("stopTask"), - taskId: z.string(), - supervisorAgentId: z.string(), + method: z.literal("stopTaskRun"), + taskRunId: z.string(), + actingAgentId: ActingAgentIdValueSchema, }) - .describe("Stops periodic execution of a task. Requires owner or admin permissions."); + .describe("Stop a task run."); -export const RemoveTaskSchema = z +export const RemoveTaskRunSchema = z .object({ - method: z.literal("removeTask"), - taskId: z.string(), - supervisorAgentId: z.string(), + method: z.literal("removeTaskRun"), + taskRunId: z.string(), + actingAgentId: ActingAgentIdValueSchema, }) - .describe("Removes a task completely. Requires owner or admin permissions."); + .describe("Removes task run."); -export const GetTaskStatusSchema = z +export const GetTaskRunSchema = z .object({ - method: z.literal("getTaskStatus"), - taskId: z.string(), - supervisorAgentId: z.string(), + method: z.literal("getTaskRun"), + taskRunId: z.string(), + actingAgentId: ActingAgentIdValueSchema, }) - .describe("Gets current status of a task. Requires agent permissions."); + .describe("Gets current state of the task run."); -export const GetAllTaskStatusesSchema = z +export const GetAllTaskRunsSchema = z .object({ - method: z.literal("getAllTaskStatuses"), - supervisorAgentId: z.string(), + method: z.literal("getAllTaskRuns"), + actingAgentId: ActingAgentIdValueSchema, }) - .describe("Gets status of all accessible tasks. Requires agent permissions."); + .describe("Gets current state of all accessible task runs."); -export const IsTaskOccupiedSchema = z +export const IsTaskRunOccupiedSchema = z .object({ - method: z.literal("isTaskOccupied"), - taskId: z.string(), - supervisorAgentId: z.string(), + method: z.literal("isTaskRunOccupied"), + taskRunId: z.string(), + actingAgentId: ActingAgentIdValueSchema, }) - .describe("Checks if a task is currently occupied. Requires current agent or owner permissions."); + .describe("Checks if a task run is currently occupied."); -export const GetTaskHistorySchema = z +export const GetTaskRunHistorySchema = z .object({ - method: z.literal("getTaskHistory"), - taskId: z.string(), - supervisorAgentId: z.string(), + method: z.literal("getTaskRunHistory"), + taskRunId: z.string(), + actingAgentId: ActingAgentIdValueSchema, options: z .object({ limit: z.number().optional(), @@ -128,48 +201,75 @@ export class TaskManagerTool extends Tool< inputSchema() { return z.discriminatedUnion("method", [ - ScheduleTaskSchema, - StartTaskSchema, - StopTaskSchema, - RemoveTaskSchema, - GetTaskStatusSchema, - GetAllTaskStatusesSchema, - IsTaskOccupiedSchema, - GetTaskHistorySchema, + CreateTaskConfigSchema, + UpdateTaskConfigSchema, + GetTaskConfigSchema, + DestroyTaskConfigSchema, + GetPoolStatsSchema, + CreateTaskRunSchema, + ScheduleStartTaskRunSchema, + StopTaskRunSchema, + RemoveTaskRunSchema, + GetTaskRunSchema, + GetAllTaskRunsSchema, + IsTaskRunOccupiedSchema, + GetTaskRunHistorySchema, ]); } protected async _run(input: ToolInput) { let data: TaskManagerToolResultData; switch (input.method) { - case "scheduleTask": - data = this.taskManager.scheduleTask(input.task, input.supervisorAgentId); + case "createTaskConfig": { + const { actingAgentId, taskConfig } = input; + data = this.taskManager.createTaskConfig(taskConfig, actingAgentId, actingAgentId); + break; + } + case "getTaskConfig": { + const { taskKind, taskType, actingAgentId } = input; + data = this.taskManager.getTaskConfig(taskKind, taskType, actingAgentId); + break; + } + case "updateTaskConfig": { + const { update: config, taskKind, taskType, actingAgentId } = input; + data = this.taskManager.updateTaskConfig({ ...config, taskKind, taskType }, actingAgentId); + break; + } + case "destroyTaskConfig": { + const { taskKind, taskType, actingAgentId } = input; + data = this.taskManager.destroyTaskConfig(taskKind, taskType, actingAgentId); + break; + } + case "getPoolStats": { + const { taskKind, taskType, actingAgentId } = input; + data = this.taskManager.getPoolStats(taskKind, taskType, actingAgentId); + break; + } + case "createTaskRun": { + const { taskKind, taskType, taskRunInput, actingAgentId } = input; + data = this.taskManager.createTaskRun(taskKind, taskType, taskRunInput, actingAgentId); break; - case "startTask": - this.taskManager.scheduleTaskStart(input.taskId, input.supervisorAgentId); - data = true; + } + case "scheduleStartTaskRun": + data = this.taskManager.scheduleStartTaskRun(input.taskRunId, input.actingAgentId); break; - case "stopTask": - data = this.taskManager.stopTask(input.taskId, input.supervisorAgentId); + case "stopTaskRun": + data = this.taskManager.stopTaskRun(input.taskRunId, input.actingAgentId); break; - case "removeTask": - data = this.taskManager.removeTask(input.taskId, input.supervisorAgentId); + case "removeTaskRun": + data = this.taskManager.destroyTaskRun(input.taskRunId, input.actingAgentId); break; - case "getTaskStatus": - data = this.taskManager.getTaskStatus(input.taskId, input.supervisorAgentId); + case "getTaskRun": + data = this.taskManager.getTaskRun(input.taskRunId, input.actingAgentId); break; - case "getAllTaskStatuses": - data = this.taskManager.getAllTaskStatuses(input.supervisorAgentId); + case "getAllTaskRuns": + data = this.taskManager.getAllTaskRuns(input.actingAgentId); break; - case "isTaskOccupied": - data = this.taskManager.isTaskOccupied(input.taskId, input.supervisorAgentId); + case "isTaskRunOccupied": + data = this.taskManager.isTaskRunOccupied(input.taskRunId, input.actingAgentId); break; - case "getTaskHistory": - data = this.taskManager.getTaskHistory( - input.taskId, - input.supervisorAgentId, - input.options, - ); + case "getTaskRunHistory": + data = this.taskManager.getTaskRunHistory(input.taskRunId, input.actingAgentId); break; } return new JSONToolOutput({ diff --git a/src/ui/agent-monitor.ts b/src/ui/agent-monitor.ts deleted file mode 100644 index 59a3ee1..0000000 --- a/src/ui/agent-monitor.ts +++ /dev/null @@ -1,661 +0,0 @@ -import blessed from "blessed"; -import chokidar from "chokidar"; -import { createReadStream } from "fs"; -import { join } from "path"; -import { createInterface } from "readline"; -import { - AgentId, - agentIdToString, - AgentPoolId, - agentPoolIdToString, - AgentPoolTypeId, - agentPoolTypeIdToString, - stringAgentIdToAgentPoolTypeId, - stringToAgentPoolId, - stringToAgentPoolTypeId, -} from "src/agents/agent-id.js"; -import { - AgentConfig, - AgentKind, - AgentKindSchema, - AvailableTool, - PoolStats, -} from "src/agents/agent-registry.js"; -import { - AgentLifecycleData, - AgentStateUpdate, - AgentUpdateTypeEnum, - AvailableToolsData, - PoolChangeData, -} from "src/agents/agent-state-logger.js"; -import { LogInit } from "src/base/audit-log.js"; -import { TaskConfig, TaskHistoryEntry } from "src/tasks/task-manager.js"; -import { updateDeepPartialObject } from "src/utils/objects.js"; -import * as st from "./ui-config.js"; - -const AGENT_LIST_DEFAULT_TEXT = "Select pool to view agents"; -const AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT = "Select agent pool to view agent config detail"; -const AGENT_DETAIL_DEFAULT_TEXT = "Select agent to view agent detail"; -const AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT = "Select agent to view lifecycle events"; - -export interface Agent { - agentId: AgentId; - inUse: boolean; - isDestroyed: boolean; - assignedTaskConfig: TaskConfig | null; - history: TaskHistoryEntry[]; -} - -export interface AgentPool { - agentPoolTypeId: AgentPoolTypeId; - agentConfig: AgentConfig; - poolStats: PoolStats; -} - -class AgentMonitor { - private screen: blessed.Widgets.Screen; - private agentPools = new Map>(); - private agentPoolList: blessed.Widgets.ListElement; - private agentPoolListItemsData: { - agentPoolId: AgentPoolId | AgentPoolTypeId; - itemContent: string; - }[] = []; - private agentPoolListSelectedIndex: number | null = null; - - private agents = new Map(); - private agentList: blessed.Widgets.ListElement; - private agentListItemsData: { - agent: Agent; - itemContent: string; - }[] = []; - private agentListSelectedIndex: number | null = null; - private agentTemplateDetail: blessed.Widgets.BoxElement; - private agentDetail: blessed.Widgets.BoxElement; - private lifecycleHistory: blessed.Widgets.BoxElement; - private logBox: blessed.Widgets.Log; - - private availableTools = new Map(); - private allAvailableTools = new Map(); - private lifecycleEvents = new Map< - string, - { timestamp: string; event: string; success: boolean; error?: string }[] - >(); - - constructor() { - this.screen = blessed.screen({ - smartCSR: true, - title: "Agent Registry Monitor", - debug: true, - }); - - // Left column - Pools and Agents (30%) - this.agentPoolList = blessed.list({ - parent: this.screen, - width: "30%", - height: "20%", - left: 0, - top: 0, - border: { type: "line" }, - label: " Agent Pools ", - style: st.UIConfig.list, - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - scrollbar: st.UIConfig.scrollbar, - }); - - this.agentList = blessed.list({ - parent: this.screen, - width: "30%", - height: "50%", - left: 0, - top: "20%", - border: { type: "line" }, - label: " Agents ", - content: AGENT_LIST_DEFAULT_TEXT, - style: st.UIConfig.list, - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - scrollbar: st.UIConfig.scrollbar, - }); - - // Center column - Details and Tools (40%) - this.agentTemplateDetail = blessed.box({ - parent: this.screen, - width: "40%", - height: "40%", - left: "30%", - top: 0, - border: { type: "line" }, - label: " Agent Config ", - content: AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT, - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - scrollbar: st.UIConfig.scrollbar, - }); - - this.agentDetail = blessed.box({ - parent: this.screen, - width: "40%", - height: "30%", - left: "30%", - top: "40%", - border: { type: "line" }, - label: " Agent Detail ", - content: AGENT_DETAIL_DEFAULT_TEXT, - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - scrollbar: st.UIConfig.scrollbar, - }); - - // Right column - Lifecycle History (30%) - this.lifecycleHistory = blessed.box({ - parent: this.screen, - width: "30%", - height: "70%", - right: 0, - top: 0, - border: { type: "line" }, - label: " Lifecycle Events ", - content: AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT, - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - scrollbar: st.UIConfig.scrollbar, - }); - - // Bottom - Live Updates - this.logBox = blessed.log({ - parent: this.screen, - width: "100%", - height: "30%", - left: 0, - bottom: 0, - border: { type: "line" }, - label: " Live Updates ", - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - scrollbar: st.UIConfig.scrollbar, - }); - - this.setupEventHandlers(); - this.screen.render(); - } - - private setupEventHandlers() { - this.screen.key(["escape", "q", "C-c"], () => process.exit(0)); - - this.agentPoolList.on("select", (_, selectedIndex) => { - this.agentPoolListSelectedIndex = selectedIndex; - const itemData = this.agentPoolListItemsData[this.agentPoolListSelectedIndex]; - if (!itemData) { - throw new Error(`Missing data for selectedIndex:${this.agentPoolListSelectedIndex}`); - } - let agentConfig; - const agentPoolId = itemData.agentPoolId; - if ((agentPoolId as AgentPoolTypeId).agentType) { - agentConfig = this.agentPools - .get(agentPoolIdToString(agentPoolId)) - ?.get(agentPoolTypeIdToString(agentPoolId as AgentPoolTypeId))?.agentConfig; - } - - this.updateAgentConfig(agentConfig, false); - this.updateAgentList(); - }); - - this.agentList.on("select", (_, selectedIndex) => { - this.agentListSelectedIndex = selectedIndex; - const itemData = this.agentListItemsData[this.agentListSelectedIndex]; - if (!itemData) { - throw new Error(`Missing data for selectedIndex:${this.agentPoolListSelectedIndex}`); - } - const { agent } = itemData; - const agentConfig = this.agentPools - .get(agentPoolIdToString(agent.agentId)) - ?.get(agentPoolTypeIdToString(agent.agentId))?.agentConfig; - this.updateAgentConfig(agentConfig, false); - this.updateAgentDetails(itemData.agent); - }); - - // Mouse scrolling for all components - [ - this.agentPoolList, - this.agentList, - this.agentTemplateDetail, - this.agentDetail, - this.lifecycleHistory, - ].forEach((component) => { - component.on("mouse", (data) => { - if (data.action === "wheelup") { - component.scroll(-1); - this.screen.render(); - } else if (data.action === "wheeldown") { - component.scroll(1); - this.screen.render(); - } - }); - }); - } - - private reset(shouldRender = true): void { - // Reset data - [this.agents, this.agentPools, this.agents, this.availableTools].forEach((m) => { - m.clear(); - }); - - // Update content - this.updateAgentPoolsList(false); - this.updateAgentConfig(undefined, false); - this.updateAgentList(false); - this.updateAgentConfig(undefined, false); - - // Reset log box - this.logBox.setContent(""); - this.logBox.log("Reading initial state from log..."); - - // Render - if (shouldRender) { - this.screen.render(); - } - } - - private updateAgentPoolsList(shouldRender = true): void { - this.agentPoolListItemsData.splice(0); - Array.from(this.agentPools.entries()) - .sort(([a], [b]) => { - // Sort agent kind - const aPoolId = stringToAgentPoolId(a); - const bPoolId = stringToAgentPoolId(b); - const aSuper = aPoolId.agentKind === AgentKindSchema.Values.supervisor; - const bSuper = bPoolId.agentKind === AgentKindSchema.Values.supervisor; - if (aSuper && !bSuper) { - return -1; - } else if (!aSuper && bSuper) { - return 1; - } else { - return aPoolId.agentKind.localeCompare(bPoolId.agentKind); - } - }) - .forEach(([agentPoolIdStr, agentTypePools]) => { - const agentPoolTypeId = stringToAgentPoolId(agentPoolIdStr); - this.agentPoolListItemsData.push({ - agentPoolId: agentPoolTypeId, - itemContent: st.agentPoolId(agentPoolTypeId), - }); - Array.from(agentTypePools.entries()) - .sort(([a], [b]) => { - // Sort agent type - const aPoolId = stringAgentIdToAgentPoolTypeId(a); - const bPoolId = stringAgentIdToAgentPoolTypeId(b); - return aPoolId.agentType.localeCompare(bPoolId.agentType); - }) - .forEach(([agentPoolTypeIdStr, agentPool]) => { - const agentPoolTypeId = stringToAgentPoolTypeId(agentPoolTypeIdStr); - this.agentPoolListItemsData.push({ - agentPoolId: agentPoolTypeId, - itemContent: st.agentPool(agentPool), - }); - }); - }); - - if (this.agentPoolListSelectedIndex == null && this.agentPoolListItemsData.length) { - this.agentPoolListSelectedIndex = 0; - this.agentPoolList.select(this.agentPoolListSelectedIndex); - } - this.agentPoolList.setItems(this.agentPoolListItemsData.map((it) => it.itemContent)); - - this.updateAgentList(false); - if (shouldRender) { - this.screen.render(); - } - } - - private updateAgentList(shouldRender = true): void { - this.agentListItemsData.splice(0); - Array.from(this.agents.entries()) - .filter(([, a]) => { - if (this.agentPoolListSelectedIndex == null) { - return false; - } - const agentPoolListItem = this.agentPoolListItemsData[this.agentPoolListSelectedIndex]; - if (agentPoolListItem.agentPoolId.agentKind === a.agentId.agentKind) { - if ((agentPoolListItem.agentPoolId as AgentPoolTypeId).agentType != null) { - return ( - (agentPoolListItem.agentPoolId as AgentPoolTypeId).agentType === a.agentId.agentType - ); - } else { - return true; - } - } - }) - .sort(([, a], [, b]) => { - const comp = a.agentId.agentType.localeCompare(b.agentId.agentType); - if (comp === 0) { - return Math.sign(a.agentId.num - b.agentId.num); - } else { - return comp; - } - }) - .forEach(([, agent]) => { - this.agentListItemsData.push({ - agent, - itemContent: st.agent(agent), - }); - }); - this.agentList.setItems(this.agentListItemsData.map((it) => it.itemContent)); - this.agentList.setContent(this.agentListItemsData.length ? "" : AGENT_LIST_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - } - - private updateAgentConfig(agentConfig?: AgentConfig, shouldRender = true): void { - if (!agentConfig) { - this.agentTemplateDetail.setContent(AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - return; - } - - const details = [ - `{bold}Agent Kind:{/bold} ${st.agentKind(agentConfig.agentKind)}`, - `{bold}Agent Type:{/bold} ${st.agentType(agentConfig.agentType)}`, - `{bold}Max Pool Size:{/bold} ${st.num(agentConfig.maxPoolSize)}`, - `{bold}Auto-populate pool:{/bold} ${st.bool(agentConfig.autoPopulatePool)}`, - "", - "{bold}Description:{/bold}", - st.desc(agentConfig.description), - "", - "{bold}Instructions:{/bold}", - st.desc(agentConfig.instructions), - "", - "{bold}Tools:{/bold}", - st.tools(this.mapTools(agentConfig.tools || [])), - ].join("\n"); - this.agentTemplateDetail.setContent(details); - if (shouldRender) { - this.screen.render(); - } - } - - private mapTools(tools: string[]): AvailableTool[] { - return tools.map( - (t) => this.allAvailableTools.get(t) ?? { name: "Undefined", description: "Lorem ipsum...." }, - ); - } - - private updateAgentDetails(agent?: Agent, shouldRender = true): void { - if (!agent) { - this.agentDetail.setContent(AGENT_DETAIL_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - return; - } - - const details = [ - `${st.label("ID")}: ${st.agentId(agent.agentId)}`, - `${st.label("In Use")}: ${st.bool(agent.inUse, "busy_idle")}`, - `${st.label("Is destroyed")}: ${st.bool(agent.isDestroyed, "inverse_color")}`, - ...(agent.assignedTaskConfig - ? [ - "", - `${st.label("Task")}: ${st.taskId(agent.assignedTaskConfig.id)}`, - `${st.label("Description")}:`, - `${st.desc(agent.assignedTaskConfig.description)}`, - `${st.label("Input")}:`, - `${st.input(agent.assignedTaskConfig.input)}`, - ] - : []), - ].join("\n"); - this.agentDetail.setContent(details); - if (shouldRender) { - this.screen.render(); - } - } - - private updateLifecycleHistory(agentId?: string, shouldRender = true): void { - if (!agentId) { - this.lifecycleHistory.setContent(AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - return; - } - - const events = this.lifecycleEvents.get(agentId) || []; - const content = events.length - ? events - .map( - ({ timestamp, event, success, error }) => - `${st.timestamp(timestamp)} ` + - `${st.eventType(event)} ` + - `${st.bool(success)}` + - (error ? `\n ${st.error(error)}` : ""), - ) - .join("\n") - : "No lifecycle events recorded"; - - this.lifecycleHistory.setContent(content); - if (shouldRender) { - this.screen.render(); - } - } - - private processLogLine(line: string, shouldRender = true): void { - try { - const update: AgentStateUpdate | LogInit = JSON.parse(line); - if (update.type === "@log_init") { - // RESET - this.reset(shouldRender); - return; - } - - let data; - let agentIdString; - let agent; - let poolId: AgentPoolId; - let poolIdStr: string; - let poolTypeId: AgentPoolTypeId; - let poolTypeIdStr: string; - let pool: Map | undefined; - let poolType: AgentPool | undefined; - switch (update.type) { - case AgentUpdateTypeEnum.AGENT_CONFIG: - data = update.data as AgentConfig; - poolId = { agentKind: data.agentKind }; - poolIdStr = agentPoolIdToString(poolId); - poolTypeId = { - agentKind: data.agentKind, - agentType: data.agentType, - } satisfies AgentPoolTypeId; - poolTypeIdStr = agentPoolTypeIdToString(poolTypeId); - pool = this.agentPools.get(poolIdStr); - if (!pool) { - poolType = { - agentPoolTypeId: poolTypeId, - agentConfig: data, - poolStats: { available: 0, created: 0, inUse: 0, poolSize: 0 }, - }; - pool = new Map([[agentPoolTypeIdToString(poolTypeId), poolType]]); - this.agentPools.set(poolIdStr, pool); - } else { - poolType = pool.get(poolTypeIdStr); - if (poolType) { - throw new Error(`PoolType ${JSON.stringify(poolTypeId)} already exists`); - } - poolType = { - agentPoolTypeId: poolTypeId, - agentConfig: data, - poolStats: { available: 0, created: 0, inUse: 0, poolSize: 0 }, - }; - pool.set(poolTypeIdStr, poolType); - } - - this.updateAgentPoolsList(false); - break; - case AgentUpdateTypeEnum.POOL: - data = update.data as PoolChangeData; - poolTypeId = { agentKind: data.agentKind, agentType: data.agentType }; - poolIdStr = agentPoolIdToString(poolTypeId); - pool = this.agentPools.get(poolIdStr); - if (!pool) { - throw new Error(`Missing pool for agentKind: ${data.agentKind}`); - } - poolTypeIdStr = agentPoolTypeIdToString(poolTypeId); - poolType = pool.get(poolTypeIdStr); - if (!poolType) { - throw new Error( - `Missing pool for agentKind: ${data.agentKind} agentType: ${data.agentType}`, - ); - } - updateDeepPartialObject(poolType.poolStats, data); - this.updateAgentPoolsList(false); - break; - case AgentUpdateTypeEnum.AVAILABLE_TOOLS: - data = update.data as AvailableToolsData; - this.availableTools.set(data.agentKind, data.availableTools ?? []); - this.allAvailableTools.clear(); - Array.from(this.availableTools.values()) - .flat() - .forEach((t) => { - this.allAvailableTools.set(t.name, t); - }); - this.updateAgentPoolsList(false); - break; - case AgentUpdateTypeEnum.AGENT: - data = update.data as AgentLifecycleData; - agentIdString = agentIdToString(data.agentId); - agent = this.agents.get(agentIdString); - - if (data.event === "onCreate") { - if (agent) { - throw new Error(`Agent ${agentIdString} is already exists`); - } - agent = { - agentId: data.agentId, - inUse: false, - isDestroyed: false, - assignedTaskConfig: null, - history: [], - } satisfies Agent; - this.agents.set(agentIdToString(data.agentId), agent); - this.updateAgentList(); - } else { - if (!agent) { - throw new Error(`Undefined agent ${agentIdString}`); - } - switch (data.event) { - case "onDestroy": - agent.isDestroyed = true; - break; - case "onAcquire": - agent.inUse = true; - agent.assignedTaskConfig = data.taskConfig!; - break; - case "onRelease": - agent.inUse = false; - agent.assignedTaskConfig = null; - agent.history.push(data.historyEntry!); - break; - } - } - - break; - } - - this.logBox.log( - `${new Date().toLocaleString()} - Event ${update.type}: update ${JSON.stringify(update.data)}`, - ); - - if (shouldRender) { - this.screen.render(); - } - } catch (error) { - if (error instanceof Error) { - this.logBox.log(`Error processing log line: ${error.message}`); - } - } - } - - private async initializeStateFromLog(logPath: string): Promise { - try { - this.logBox.log("Reading initial state from log..."); - - const rl = createInterface({ - input: createReadStream(logPath, { encoding: "utf8" }), - crlfDelay: Infinity, - }); - - for await (const line of rl) { - this.processLogLine(line); - } - - this.logBox.log(`Initial state loaded: ${this.agents.size} agents found`); - this.updateAgentList(); - } catch (error) { - if (error instanceof Error) { - this.logBox.log(`Error reading initial state: ${error.message}`); - } - } - } - - private watchLogFile(logPath: string): void { - let lastProcessedSize = 0; - - chokidar - .watch(logPath, { - persistent: true, - usePolling: true, - interval: 100, - }) - .on("change", async (path) => { - try { - const rl = createInterface({ - input: createReadStream(path, { encoding: "utf8", start: lastProcessedSize }), - crlfDelay: Infinity, - }); - - for await (const line of rl) { - this.processLogLine(line); - lastProcessedSize += Buffer.from(line).length + 1; - } - } catch (error) { - if (error instanceof Error) { - this.logBox.log(`Error processing log update: ${error.message}`); - } - } - }); - } - - public async start(): Promise { - const logPath = join(process.cwd(), "logs", "agent_state.log"); - - // First read the entire log to build initial state - await this.initializeStateFromLog(logPath); - - // Then start watching for changes - this.watchLogFile(logPath); - } -} - -// Start the monitor -const monitor = new AgentMonitor(); -monitor.start(); diff --git a/src/ui/agent-monitor/monitor.ts b/src/ui/agent-monitor/monitor.ts new file mode 100644 index 0000000..1a727c4 --- /dev/null +++ b/src/ui/agent-monitor/monitor.ts @@ -0,0 +1,590 @@ +import blessed from "blessed"; +import { join } from "path"; +import { clone } from "remeda"; +import { + AgentConfigId, + AgentKindId, + agentSomeIdToTypeValue, + AgentTypeId, + stringToAgent, + stringToAgentConfig, + stringToAgentKind, +} from "src/agents/agent-id.js"; +import { AgentConfig, AgentKindEnumSchema, AvailableTool } from "src/agents/registry/dto.js"; +import { AgentInfo, AgentStateBuilder, StateUpdateType } from "src/agents/state/builder.js"; +import * as st from "../config.js"; +import { BaseMonitor, ParentInput, ScreenInput } from "../base/monitor.js"; + +const AGENT_LIST_DEFAULT_TEXT = "Select pool to view agents"; +const AGENT_VERSION_DEFAULT_TEXT = "Select pool to view versions"; +const AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT = "Select agent pool to view agent config detail"; +const AGENT_DETAIL_DEFAULT_TEXT = "Select agent to view agent detail"; +const AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT = "Select agent to view lifecycle events"; + +export class AgentMonitor extends BaseMonitor { + private stateBuilder: AgentStateBuilder; + private agentPoolList: blessed.Widgets.ListElement; + private agentPoolListItemsData: { + agentTypeId: AgentTypeId | AgentKindId; + itemContent: string; + }[] = []; + private agentPoolListSelectedIndex: number | null = null; + + private agentVersionsList: blessed.Widgets.ListElement; + private agentVersionsListItemsData: { + agentTypeId: AgentConfigId; + itemContent: string; + }[] = []; + private agentVersionsListSelectedIndex: number | null = null; + + private agentList: blessed.Widgets.ListElement; + private agentListItemsData: { + agent: AgentInfo; + itemContent: string; + }[] = []; + private agentListSelectedIndex: number | null = null; + + private agentConfigDetail: blessed.Widgets.BoxElement; + private agentDetail: blessed.Widgets.BoxElement; + private lifecycleHistory: blessed.Widgets.BoxElement; + private logBox: blessed.Widgets.Log; + + private lifecycleEvents = new Map< + string, + { timestamp: string; event: string; success: boolean; error?: string }[] + >(); + + constructor(arg: ParentInput | ScreenInput) { + super(arg); + this.stateBuilder = new AgentStateBuilder(); + this.stateBuilder.on("log:reset", () => { + this.reset(); + }); + this.stateBuilder.on("log:new_line", (line) => { + this.logBox.log(`${new Date().toLocaleString()} - ${line}`); + }); + this.stateBuilder.on("state:updated", (update) => { + switch (update.type) { + case StateUpdateType.TOOLS: + case StateUpdateType.AGENT_CONFIG: + case StateUpdateType.POOL: + this.updateAgentPoolsList(false); + break; + case StateUpdateType.AGENT: + this.updateAgentVersionsList(false); + this.updateAgentDetails(); + break; + case StateUpdateType.ASSIGNMENT: + this.updateAgentDetails(); + break; + case StateUpdateType.FULL: + // Full refresh + // this.reset(); + break; + } + }); + + this.stateBuilder.on("error", (error: Error) => { + // OK + console.error("Error occurred:", error); + }); + + // Left column - Pools and Agents (30%) + this.agentPoolList = blessed.list({ + parent: this.parent, + width: "30%", + height: "20%", + left: 0, + top: 0, + border: { type: "line" }, + label: " Agent Pools ", + style: st.UIConfig.list, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + this.agentVersionsList = blessed.list({ + parent: this.parent, + width: "30%", + height: "20%", + left: 0, + top: "20%", + border: { type: "line" }, + label: " Agent Versions ", + content: AGENT_VERSION_DEFAULT_TEXT, + style: st.UIConfig.list, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + this.agentList = blessed.list({ + parent: this.parent, + width: "30%", + height: "30%", + left: 0, + top: "40%", + border: { type: "line" }, + label: " Agents ", + content: AGENT_LIST_DEFAULT_TEXT, + style: st.UIConfig.list, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + // Center column - Details and Tools (40%) + this.agentConfigDetail = blessed.box({ + parent: this.parent, + width: "40%", + height: "40%", + left: "30%", + top: 0, + border: { type: "line" }, + label: " Agent Config ", + content: AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + this.agentDetail = blessed.box({ + parent: this.parent, + width: "40%", + height: "30%", + left: "30%", + top: "40%", + border: { type: "line" }, + label: " Agent Detail ", + content: AGENT_DETAIL_DEFAULT_TEXT, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + // Right column - Lifecycle History (30%) + this.lifecycleHistory = blessed.box({ + parent: this.parent, + width: "30%", + height: "70%", + right: 0, + top: 0, + border: { type: "line" }, + label: " Lifecycle Events ", + content: AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + // Bottom - Live Updates + this.logBox = blessed.log({ + parent: this.parent, + width: "100%", + height: "30%", + left: 0, + bottom: 0, + border: { type: "line" }, + label: " Live Updates ", + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + this.setupEventHandlers(); + this.screen.render(); + } + + private setupEventHandlers() { + this.screen.key(["escape", "q", "C-c"], () => process.exit(0)); + + this.agentPoolList.on("select", (_, selectedIndex) => { + this.agentPoolListSelectedIndex = selectedIndex; + const itemData = this.agentPoolListItemsData[this.agentPoolListSelectedIndex]; + if (!itemData) { + throw new Error( + `Missing data for selected pool on index:${this.agentPoolListSelectedIndex}`, + ); + } + let agentConfig; + const agentTypeId = itemData.agentTypeId; + if ((agentTypeId as AgentTypeId).agentType) { + agentConfig = this.stateBuilder.getAgentConfig( + agentSomeIdToTypeValue(agentTypeId as AgentTypeId), + ); + } + + this.updateAgentConfig(agentConfig, false); + this.updateAgentVersionsList(); + }); + + this.agentVersionsList.on("select", (_, selectedIndex) => { + this.agentVersionsListSelectedIndex = selectedIndex; + const itemData = this.agentVersionsListItemsData[this.agentVersionsListSelectedIndex]; + if (!itemData) { + throw new Error(`Missing data for selectedIndex:${this.agentVersionsListSelectedIndex}`); + } + + const agentConfigId = itemData.agentTypeId as AgentConfig; + const agentConfig = this.stateBuilder.getAgentConfig( + agentSomeIdToTypeValue(agentConfigId), + agentConfigId.agentConfigVersion, + ); + + this.updateAgentConfig(agentConfig, false); + this.updateAgentVersionsList(); + }); + + this.agentList.on("select", (_, selectedIndex) => { + this.agentListSelectedIndex = selectedIndex; + const itemData = this.agentListItemsData[this.agentListSelectedIndex]; + if (!itemData) { + throw new Error(`Missing data for selectedIndex:${this.agentPoolListSelectedIndex}`); + } + const { agent } = itemData; + const agentConfigId = stringToAgentConfig(agent.agentConfigId); + const agentTypeId = agentSomeIdToTypeValue(agentConfigId); + const agentConfig = this.stateBuilder.getAgentConfig( + agentTypeId, + agentConfigId.agentConfigVersion, + ); + this.updateAgentConfig(agentConfig, false); + this.updateAgentDetails(itemData.agent); + }); + + // Mouse scrolling for all components + [ + this.agentPoolList, + this.agentList, + this.agentConfigDetail, + this.agentDetail, + this.lifecycleHistory, + ].forEach((component) => { + component.on("mouse", (data) => { + if (data.action === "wheelup") { + component.scroll(-1); + this.screen.render(); + } else if (data.action === "wheeldown") { + component.scroll(1); + this.screen.render(); + } + }); + }); + } + + private reset(shouldRender = true): void { + // Reset selections + this.agentListSelectedIndex = null; + this.agentPoolListSelectedIndex = null; + this.agentVersionsListSelectedIndex = null; + + // Update content + this.updateAgentPoolsList(false); + this.updateAgentConfig(undefined, false); + this.updateAgentList(false); + + // Reset log box + this.logBox.setContent(""); + this.logBox.log("Reading initial state from log..."); + + // Render + if (shouldRender) { + this.screen.render(); + } + } + + private updateAgentPoolsList(shouldRender = true): void { + this.agentPoolListItemsData.splice(0); + const state = this.stateBuilder.getState(); + Array.from(state.agentPools.entries()) + .sort(([a], [b]) => { + // Sort agent kind + const aPoolId = stringToAgentKind(a); + const bPoolId = stringToAgentKind(b); + const aSuper = aPoolId.agentKind === AgentKindEnumSchema.Values.supervisor; + const bSuper = bPoolId.agentKind === AgentKindEnumSchema.Values.supervisor; + if (aSuper && !bSuper) { + return -1; + } else if (!aSuper && bSuper) { + return 1; + } else { + return aPoolId.agentKind.localeCompare(bPoolId.agentKind); + } + }) + .forEach(([agentKindStr, agentTypePools]) => { + const agentKindId = stringToAgentKind(agentKindStr); + this.agentPoolListItemsData.push({ + agentTypeId: agentKindId, + itemContent: st.agentKindId(agentKindId), + }); + Array.from(agentTypePools.entries()) + .sort(([a], [b]) => { + // Sort agent type + return a.localeCompare(b); + }) + .forEach(([agentTypeStr, agentPool]) => { + this.agentPoolListItemsData.push({ + agentTypeId: { agentKind: agentKindId.agentKind, agentType: agentTypeStr }, + itemContent: st.agentPool(agentPool), + }); + }); + }); + + if (this.agentPoolListSelectedIndex == null && this.agentPoolListItemsData.length) { + this.agentPoolListSelectedIndex = 0; + this.agentPoolList.select(this.agentPoolListSelectedIndex); + } + this.agentPoolList.setItems(this.agentPoolListItemsData.map((it) => it.itemContent)); + + this.updateAgentVersionsList(false); + if (shouldRender) { + this.screen.render(); + } + } + + private updateAgentVersionsList(shouldRender = true): void { + this.agentVersionsListItemsData.splice(0); + + if (this.agentPoolListSelectedIndex != null) { + // Get versions of selected agent pool + const itemData = this.agentPoolListItemsData[this.agentPoolListSelectedIndex]; + if (!itemData) { + throw new Error( + `Missing data for selected pool on index:${this.agentPoolListSelectedIndex}`, + ); + } + + const agentPoolTypeId = itemData.agentTypeId as AgentTypeId; + if (agentPoolTypeId.agentType != null) { + // List versions + const agentPool = this.stateBuilder.getAgentPool( + agentPoolTypeId.agentKind, + agentPoolTypeId.agentType, + ); + if (agentPool) { + const hasMultipleVersions = agentPool.versions.length > 1; + if (hasMultipleVersions) { + this.agentPoolListItemsData.push({ + agentTypeId: { + agentKind: agentPoolTypeId.agentKind, + agentType: agentPoolTypeId.agentType, + }, + itemContent: st.versionAgentPoolStats("all"), + }); + } + + clone(agentPool.versions) + .reverse() + .forEach(([agentConfigVersion, poolStats]) => { + const agentTypeId = { + agentKind: agentPoolTypeId.agentKind, + agentType: agentPoolTypeId.agentType, + agentConfigVersion, + }; + this.agentVersionsListItemsData.push({ + agentTypeId, + itemContent: st.versionAgentPoolStats(st.versionNum(agentConfigVersion), poolStats), + }); + }); + } + } + } + + this.agentVersionsList.setItems(this.agentVersionsListItemsData.map((it) => it.itemContent)); + this.agentVersionsList.setContent( + this.agentVersionsListItemsData.length ? "" : AGENT_VERSION_DEFAULT_TEXT, + ); + this.updateAgentList(false); + if (shouldRender) { + this.screen.render(); + } + } + + private updateAgentList(shouldRender = true): void { + this.agentListItemsData.splice(0); + if (this.agentVersionsListSelectedIndex != null) { + Array.from(this.stateBuilder.getAllAgents()) + .filter((a) => { + if (this.agentVersionsListSelectedIndex == null) { + return false; + } + + const agentId = stringToAgent(a.agentId); + const agentPoolListItem = + this.agentVersionsListItemsData[this.agentVersionsListSelectedIndex]; + if (agentPoolListItem && agentPoolListItem.agentTypeId.agentKind === agentId.agentKind) { + if ((agentPoolListItem.agentTypeId as AgentTypeId).agentType != null) { + if ((agentPoolListItem.agentTypeId as AgentTypeId).agentType == agentId.agentType) { + if ((agentPoolListItem.agentTypeId as AgentConfigId).agentConfigVersion != null) { + return ( + (agentPoolListItem.agentTypeId as AgentConfigId).agentConfigVersion === + agentId.agentConfigVersion + ); + } + } else { + return false; + } + } + return true; + } + return false; + }) + .sort((a, b) => { + const aAgentId = stringToAgent(a.agentId); + const bAgentId = stringToAgent(b.agentId); + + if (aAgentId.agentConfigVersion != bAgentId.agentConfigVersion) { + return Math.sign(aAgentId.agentConfigVersion - bAgentId.agentConfigVersion); + } + + const comp = aAgentId.agentType.localeCompare(bAgentId.agentType); + if (comp === 0) { + return Math.sign(aAgentId.agentNum - bAgentId.agentNum); + } else { + return comp; + } + }) + .forEach((agent) => { + this.agentListItemsData.push({ + agent, + itemContent: st.agent(agent), + }); + }); + } + + this.agentList.setItems(this.agentListItemsData.map((it) => it.itemContent)); + this.agentList.setContent(this.agentListItemsData.length ? "" : AGENT_LIST_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + } + + private updateAgentConfig(agentConfig?: AgentConfig, shouldRender = true): void { + if (!agentConfig) { + this.agentConfigDetail.setContent(AGENT_TEMPLATE_DETAIL_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + + const details = [ + `{bold}Id:{/bold} ${st.agentConfigId(agentConfig.agentConfigId)}`, + `{bold}Version:{/bold} ${st.versionNum(agentConfig.agentConfigVersion)}`, + `{bold}Agent Kind:{/bold} ${st.agentKind(agentConfig.agentKind)}`, + `{bold}Agent Type:{/bold} ${st.agentType(agentConfig.agentType)}`, + `{bold}Max Pool Size:{/bold} ${st.num(agentConfig.maxPoolSize)}`, + `{bold}Auto-populate pool:{/bold} ${st.bool(agentConfig.autoPopulatePool)}`, + "", + "{bold}Description:{/bold}", + st.desc(agentConfig.description), + "", + "{bold}Instructions:{/bold}", + st.desc(agentConfig.instructions), + "", + ...(agentConfig.tools.length + ? ["{bold}Tools:{/bold}", st.tools(this.mapTools(agentConfig.tools))] + : []), + ].join("\n"); + this.agentConfigDetail.setContent(details); + if (shouldRender) { + this.screen.render(); + } + } + + private mapTools(tools: string[]): AvailableTool[] { + return tools.map( + (t) => + this.stateBuilder.getAllTools().get(t) ?? { + name: "Undefined", + description: "Lorem ipsum....", + }, + ); + } + + private updateAgentDetails(agent?: AgentInfo, shouldRender = true): void { + if (!agent) { + this.agentDetail.setContent(AGENT_DETAIL_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + + const details = [ + `${st.label("Id")}: ${st.agentId(stringToAgent(agent.agentId))}`, + `${st.label("In Use")}: ${st.bool(agent.inUse, "busy_idle")}`, + `${st.label("Is destroyed")}: ${st.bool(agent.isDestroyed, "inverse_color")}`, + // ...(agent.assignedTaskConfig + // ? [ + // "", + // `${st.label("Task")}: ${st.taskId(agent.assignedTaskConfig.id)}`, + // `${st.label("Description")}:`, + // `${st.desc(agent.assignedTaskConfig.description)}`, + // `${st.label("Input")}:`, + // `${st.input(agent.assignedTaskConfig.input)}`, + // ] + // : []), + ].join("\n"); + this.agentDetail.setContent(details); + if (shouldRender) { + this.screen.render(); + } + } + + private updateLifecycleHistory(agentId?: string, shouldRender = true): void { + if (!agentId) { + this.lifecycleHistory.setContent(AGENT_LIFECYCLE_HISTORY_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + + const events = this.lifecycleEvents.get(agentId) || []; + const content = events.length + ? events + .map( + ({ timestamp, event, success, error }) => + `${st.timestamp(timestamp)} ` + + `${st.eventType(event)} ` + + `${st.bool(success)}` + + (error ? `\n ${st.error(error)}` : ""), + ) + .join("\n") + : "No lifecycle events recorded"; + + this.lifecycleHistory.setContent(content); + if (shouldRender) { + this.screen.render(); + } + } + + public async start(): Promise { + const logPath = join(process.cwd(), "logs", "agent_state.log"); + // First read the entire log to build initial state + await this.stateBuilder.watchLogFile(logPath); + } +} diff --git a/src/ui/base/monitor.ts b/src/ui/base/monitor.ts new file mode 100644 index 0000000..9cb2bcb --- /dev/null +++ b/src/ui/base/monitor.ts @@ -0,0 +1,30 @@ +import blessed from "blessed"; + +export interface ParentInput { + screen: blessed.Widgets.Screen; + parent: blessed.Widgets.BoxElement; +} + +export interface ScreenInput { + title: string; +} + +export abstract class BaseMonitor { + protected screen: blessed.Widgets.Screen; + protected parent: blessed.Widgets.Node; + + constructor(arg: ParentInput | ScreenInput) { + if ((arg as ScreenInput).title) { + this.screen = blessed.screen({ + smartCSR: true, + title: (arg as ScreenInput).title, + debug: true, + }); + this.parent = this.screen; + } else { + const { screen, parent } = arg as ParentInput; + this.screen = screen; + this.parent = parent; + } + } +} diff --git a/src/ui/colors.ts b/src/ui/colors.ts new file mode 100644 index 0000000..28e4bd4 --- /dev/null +++ b/src/ui/colors.ts @@ -0,0 +1,360 @@ +// Source: https://encycolorpedia.com/named + +export const RedColors = { + red: "#FF0000", + crimson: "#DC143C", + fire_brick: "#B22222", + dark_red: "#8B0000", + blood_red: "#660000", + burgundy: "#800020", + cardinal: "#C41E3A", + carmine: "#960018", + cornell_red: "#B31B1B", + dark_candy_apple_red: "#A40000", + electric_red: "#E60000", + falu_red: "#801818", + imperial_red: "#ED2939", + indian_red: "#CD5C5C", + light_carmine_pink: "#E66771", + madder_lake: "#CC3336", + ou_crimson_red: "#841617", + persian_red: "#CC3333", + rose_madder: "#E32636", + rosewood: "#65000B", + rufous: "#A81C07", + rusty_red: "#DA2C43", + upsdell_red: "#AE2029", + venetian_red: "#C80815", + vivid_red: "#F70D1A", +} as const; + +export const PinkColors = { + pink: "#FFC0CB", + light_pink: "#FFB6C1", + hot_pink: "#FF69B4", + deep_pink: "#FF1493", + medium_violet_red: "#C71585", + pale_violet_red: "#DB7093", + amaranth_pink: "#F19CBB", + baker_miller_pink: "#FF91AF", + charm_pink: "#E68FAC", + cherry_blossom_pink: "#FFB7C5", + cotton_candy: "#FFBCD9", + flamingo_pink: "#FC8EAC", + french_pink: "#FD6C9E", + lavender_pink: "#FBAED2", + magenta_pink: "#CC338B", + orchid_pink: "#F2BDCD", + persian_pink: "#F77FBE", + piggy_pink: "#FDDDE6", + rose_pink: "#FF66CC", + shocking_pink: "#FC0FC0", + steel_pink: "#CC33CC", + tickle_me_pink: "#FC89AC", +} as const; + +export const OrangeColors = { + orange: "#FFA500", + asda_orange: "#C86500", + dark_orange: "#FF8C00", + burnt_orange: "#CC5500", + coral: "#FF7F50", + international_orange: "#FF4F00", + orange_peel: "#FF9F00", + princeton_orange: "#F58025", + safety_orange: "#FF6700", + spanish_orange: "#E86100", + tangerine: "#F28500", + tigers_eye: "#E08D3C", + ut_orange: "#FF8200", + blush_orange: "#FF6F5E", + carrot_orange: "#ED9121", + flame: "#E25822", + giants_orange: "#FE5A1D", + mandarin: "#F37A48", + orange_red: "#FF4500", + pumpkin: "#FF7518", + tangelo: "#F94D00", +} as const; + +export const YellowColors = { + yellow: "#EEFF00", + dark_yellow: "#999900", + light_yellow: "#FFFFE0", + lemon_chiffon: "#FFFACD", + cream: "#FFFDD0", + corn: "#FBEC5D", + golden_yellow: "#FFDF00", + icterine: "#FCF75E", + jasmine: "#F8DE7E", + jonquil: "#F4CA16", + lemon: "#FFF700", + lemon_yellow: "#FFF44F", + maize: "#FBEC5D", + maximum_yellow: "#FAFA37", + naples_yellow: "#FADA5E", + school_bus_yellow: "#FFD800", + selective_yellow: "#FFBA00", + sunglow: "#FFCC33", + unmellow_yellow: "#FFFF66", + yellow_rose: "#FFF000", + canary_yellow: "#FFEF00", +} as const; + +export const PurpleColors = { + purple: "#800080", + rebecca_purple: "#663399", + dark_purple: "#301934", + electric_purple: "#BF00FF", + fuchsia: "#FF00FF", + heliotrope: "#DF73FF", + lavender: "#E6E6FA", + magenta: "#FF00FF", + mauve: "#E0B0FF", + orchid: "#DA70D6", + plum: "#8E4585", + purple_heart: "#69359C", + purple_mountain_majesty: "#9678B6", + purple_pizzazz: "#FE4EDA", + purple_taupe: "#50404D", + royal_purple: "#7851A9", + twilight_lavender: "#8A496B", + tyrian_purple: "#66023C", + violet: "#8601AF", + wisteria: "#C9A0DC", +} as const; + +export const GreenColors = { + green: "#008000", + dark_green: "#006400", + forest_green: "#228B22", + lime_green: "#32CD32", + pale_green: "#98FB98", + sea_green: "#2E8B57", + spring_green: "#00FF7F", + teal: "#008080", + army_green: "#4B5320", + asparagus: "#87A96B", + bottle_green: "#006A4E", + british_racing_green: "#004225", + brunswick_green: "#1B4D3E", + cambridge_blue: "#A3C1AD", + celadon: "#ACE1AF", + dartmouth_green: "#00703C", + emerald: "#50C878", + feldgrau: "#4D5D53", + fern_green: "#4F7942", + hunter_green: "#355E3B", + india_green: "#138808", + jungle_green: "#29AB87", + kelly_green: "#4CBB17", + malachite: "#0BDA51", + mint: "#3EB489", + moss_green: "#8A9A5B", + msu_green: "#18453B", + office_green: "#008000", + pakistan_green: "#006600", + persian_green: "#00A693", + pine_green: "#01796F", + sap_green: "#507D2A", + shamrock_green: "#009E60", + viridian: "#40826D", +} as const; + +export const BlueColors = { + blue: "#0000FF", + navy_blue: "#000080", + powder_blue: "#B0E0E6", + royal_blue: "#4169E1", + sky_blue: "#87CEEB", + steel_blue: "#4682B4", + alice_blue: "#F0F8FF", + azure: "#007FFF", + baby_blue: "#89CFF0", + carolina_blue: "#56A0D3", + celeste: "#B2FFFF", + cerulean: "#2A52BE", + cerulean_frost: "#6D9BC3", + cobalt_blue: "#0047AB", + columbia_blue: "#C4D8E2", + cornflower_blue: "#6495ED", + cyan: "#00FFFF", + denim: "#1560BD", + egyptian_blue: "#1034A6", + electric_blue: "#7DF9FF", + glaucous: "#6082B6", + international_klein_blue: "#002FA7", + maya_blue: "#73C2FB", + midnight_blue: "#191970", + oxford_blue: "#002147", + persian_blue: "#1C39BB", + sapphire: "#0F52BA", + teal_blue: "#367588", + true_blue: "#2D68C4", + turquoise: "#40E0D0", + ultramarine: "#120A8F", + yale_blue: "#0F4D92", +} as const; + +export const BrownColors = { + brown: "#964B00", + chocolate: "#7B3F00", + copper: "#B87333", + desert_sand: "#EDC9AF", + khaki: "#C3B091", + saddle_brown: "#8B4513", + sandy_brown: "#F4A460", + beaver: "#9F8170", + beige: "#F5F5DC", + bistre: "#3D2B1F", + bronze: "#CD7F32", + buff: "#F0DC82", + burgundy: "#800020", + burnt_sienna: "#E97451", + burnt_umber: "#8A3324", + cafe_au_lait: "#A67B5B", + camel: "#C19A6B", + chestnut: "#954535", + cinnamon: "#D2691E", + cocoa_brown: "#D2691E", + coffee: "#6F4E37", + dark_brown: "#654321", + desert: "#C19A6B", + dirt: "#9B7653", + fawn: "#E5AA70", + field_drab: "#6C541E", + golden_brown: "#996515", + liver: "#674C47", + mahogany: "#C04000", + maroon: "#800000", + ochre: "#CC7722", + raw_umber: "#826644", + redwood: "#A45A52", + rosy_brown: "#BC8F8F", + russet: "#80461B", + rust: "#B7410E", + sepia: "#704214", + sienna: "#882D17", + tan: "#D2B48C", + taupe: "#483C32", + umber: "#635147", + walnut_brown: "#5C5248", + wood_brown: "#C19A6B", +} as const; + +export const WhiteColors = { + white: "#FFFFFF", + snow: "#FFFAFA", + honeydew: "#F0FFF0", + mint_cream: "#F5FFFA", + azure: "#F0FFFF", + alice_blue: "#F0F8FF", + ghost_white: "#F8F8FF", + white_smoke: "#F5F5F5", + seashell: "#FFF5EE", + beige: "#F5F5DC", + old_lace: "#FDF5E6", + floral_white: "#FFFAF0", + ivory: "#FFFFF0", + antique_white: "#FAEBD7", + linen: "#FAF0E6", + lavender_blush: "#FFF0F5", + misty_rose: "#FFE4E1", + cosmic_latte: "#FFF8E7", + cream: "#FFFDD0", + eggshell: "#F0EAD6", + champagne: "#F7E7CE", + vanilla: "#F3E5AB", +} as const; + +export const GrayColors = { + gray: "#202020", + dim_gray: "#696969", + light_gray: "#D3D3D3", + slate_gray: "#708090", + dark_gray: "#A9A9A9", + silver: "#C0C0C0", + gainsboro: "#DCDCDC", + ash_gray: "#B2BEB5", + battleship_gray: "#848482", + cadet_gray: "#91A3B0", + charcoal: "#36454F", + cinereous: "#98817B", + cool_gray: "#8C92AC", + davy_gray: "#555555", + feldgrau: "#4D5D53", + granite_gray: "#676767", + gunmetal: "#2A3439", + marengo: "#4C5866", + nickel: "#727472", + onyx: "#353839", + outer_space: "#414A4C", + payne_gray: "#536878", + platinum: "#E5E4E2", + quick_silver: "#A6A6A6", + roman_silver: "#838996", + silver_sand: "#BFC1C2", + sonic_silver: "#757575", + taupe: "#483C32", + timberwolf: "#DBD7D2", +} as const; + +export const BlackColors = { + black: "#000000", + eerie_black: "#1B1B1B", + jet: "#343434", + licorice: "#1A1110", + night: "#0C090A", + onyx: "#353839", + raisin_black: "#242124", + charleston_green: "#232B2B", + ebony: "#555D50", + dark_jungle_green: "#1A2421", + vampire_black: "#080808", + black_olive: "#3B3C36", + black_leather_jacket: "#253529", +} as const; + +export type RedColorsType = typeof RedColors; +export type PinkColorsType = typeof PinkColors; +export type OrangeColorsType = typeof OrangeColors; +export type YellowColorsType = typeof YellowColors; +export type PurpleColorsType = typeof PurpleColors; +export type GreenColorsType = typeof GreenColors; +export type BlueColorsType = typeof BlueColors; +export type BrownColorsType = typeof BrownColors; +export type WhiteColorsType = typeof WhiteColors; +export type GrayColorsType = typeof GrayColors; +export type BlackColorsType = typeof BlackColors; + +export type RedColorNames = keyof typeof RedColors; +export type PinkColorNames = keyof typeof PinkColors; +export type OrangeColorNames = keyof typeof OrangeColors; +export type YellowColorNames = keyof typeof YellowColors; +export type PurpleColorNames = keyof typeof PurpleColors; +export type GreenColorNames = keyof typeof GreenColors; +export type BlueColorNames = keyof typeof BlueColors; +export type BrownColorNames = keyof typeof BrownColors; +export type WhiteColorNames = keyof typeof WhiteColors; +export type GrayColorNames = keyof typeof GrayColors; +export type BlackColorNames = keyof typeof BlackColors; + +export const UIColors = { + red: RedColors, + pink: PinkColors, + orange: OrangeColors, + yellow: YellowColors, + purple: PurpleColors, + green: GreenColors, + blue: BlueColors, + brown: BrownColors, + white: WhiteColors, + gray: GrayColors, + black: BlackColors, +} as const; + +// Type for the UIColors object +export type UIColorsType = typeof UIColors; + +// Type for the color names +export type UIColorNames = keyof typeof UIColors; diff --git a/src/ui/config.ts b/src/ui/config.ts new file mode 100644 index 0000000..5b845dc --- /dev/null +++ b/src/ui/config.ts @@ -0,0 +1,410 @@ +import { clone } from "remeda"; +import { + AgentId, + AgentKindId, + AgentTypeId, + stringToAgent, + stringToAgentType, +} from "src/agents/agent-id.js"; +import { + AgentConfigPoolStats, + AgentKindEnum, + AgentKindEnumSchema, + AgentKindValue, + AgentTypeValue, + AvailableTool, +} from "src/agents/registry/dto.js"; +import { AgentInfo } from "src/agents/state/builder.js"; +import { + TaskConfigIdValue, + TaskConfigPoolStats, + TaskKindEnum, + TaskKindEnumSchema, + TaskKindValue, + TaskRunStatusEnum, + TaskTypeValue, +} from "src/tasks/manager/dto.js"; +import { + stringToTaskRun, + stringToTaskType, + TaskKindId, + TaskRunId, + TaskTypeId, +} from "src/tasks/task-id.js"; +import { UIColors } from "./colors.js"; +import { TaskRunInfo } from "src/tasks/state/builder.js"; + +export interface StyleItem { + fg?: string; + bold?: boolean; + icon?: string; +} +export type StyleItemVersioned = Record; +export type StyleItemValue = StyleItem | StyleItemVersioned; + +export type StyleCategory = Record; + +export const DEFAULT_VERSION = "default"; +export const BUSY_IDLE = "busy_idle"; +export const INVERSE_COLOR = "inverse_color"; +export const AMBIENT_VERSION = "ambient"; + +export const UIConfig = { + labels: { + default: { fg: UIColors.white.white, bold: true }, + taskId: { fg: UIColors.orange.asda_orange, bold: true }, + status: { fg: UIColors.white.white, bold: true }, + agentKind: { fg: UIColors.purple.magenta, bold: true }, + taskKind: { fg: UIColors.purple.magenta, bold: true }, + agentType: { fg: UIColors.blue.cyan, bold: true }, + taskType: { fg: UIColors.blue.cyan, bold: true }, + agentId: { + [AgentKindEnumSchema.Values.supervisor]: { + fg: UIColors.brown.saddle_brown, + bold: true, + icon: "⬢", + }, + [AgentKindEnumSchema.Values.operator]: { + fg: UIColors.brown.saddle_brown, + bold: true, + icon: "⬡", + }, + }, + taskRunId: { + [TaskKindEnumSchema.Values.supervisor]: { + fg: UIColors.brown.saddle_brown, + bold: true, + icon: "■", + }, + [TaskKindEnumSchema.Values.operator]: { + fg: UIColors.brown.saddle_brown, + bold: true, + icon: "□", + }, + }, + agentTypeId: { fg: UIColors.white.white }, + taskTypeId: { fg: UIColors.white.white }, + owner: { fg: UIColors.brown.saddle_brown, bold: true }, + description: { fg: UIColors.blue.cerulean, bold: false }, + input: { + [DEFAULT_VERSION]: { fg: UIColors.yellow.yellow, bold: false }, + [AMBIENT_VERSION]: { fg: UIColors.yellow.corn, bold: false }, + }, + output: { + [DEFAULT_VERSION]: { fg: UIColors.green.green, bold: false }, + [AMBIENT_VERSION]: { fg: UIColors.green.sea_green, bold: false }, + }, + error: { fg: UIColors.red.red, bold: true }, + executionTime: { fg: UIColors.yellow.yellow }, + timestamp: { fg: UIColors.gray.gray }, + tool: { fg: UIColors.blue.cyan, icon: "⚒" }, + eventType: { + fg: UIColors.yellow.yellow, + bold: true, + icon: "⚡", + }, + version: { fg: UIColors.gray.cool_gray, bold: false }, + } satisfies StyleCategory, + + status: { + CREATED: { fg: UIColors.yellow.yellow, icon: "◆" }, // Diamond + EXECUTING: { fg: UIColors.green.green, icon: "▶" }, // Play triangle + SCHEDULED: { fg: UIColors.blue.cyan, icon: "◇" }, // Hollow diamond + WAITING: { fg: UIColors.blue.cyan, icon: "◆" }, // Hollow diamond + FAILED: { fg: UIColors.red.red, icon: "×" }, // Square + COMPLETED: { fg: UIColors.blue.blue, icon: "●" }, // Circle + STOPPED: { fg: UIColors.gray.gray, icon: "◼" }, // Filled square + } satisfies StyleCategory, + + BUSY: { + fg: UIColors.red.red, + bg: null, + bold: true, + prefix: "⚡", + suffix: "", + }, + IDLE: { + fg: UIColors.green.green, + bg: null, + bold: false, + prefix: "○", + suffix: "", + }, + + boolean: { + TRUE: { + [DEFAULT_VERSION]: { fg: UIColors.green.green, icon: "[✓]" }, + [INVERSE_COLOR]: { fg: UIColors.red.red, icon: "[✓]" }, + [BUSY_IDLE]: { + fg: UIColors.red.red, + bold: true, + icon: "⚡", + }, + }, + FALSE: { + [DEFAULT_VERSION]: { fg: UIColors.red.red, icon: "[✕]" }, + [INVERSE_COLOR]: { fg: UIColors.green.green, icon: "[✕]" }, + [BUSY_IDLE]: { + fg: UIColors.green.green, + bold: false, + icon: "○", + }, + }, + } satisfies StyleCategory, + + borders: { + type: "line", + fg: UIColors.white.white, + }, + + list: { + selected: { bg: UIColors.blue.blue, fg: UIColors.white.white }, + border: { fg: UIColors.white.white }, + item: { + hover: { bg: UIColors.blue.blue }, + }, + }, + + scrollbar: { + ch: " ", + track: { bg: UIColors.gray.gray }, + style: { inverse: true }, + }, + number: { + positive: { fg: UIColors.green.green, bold: true }, + neutral: { fg: UIColors.gray.battleship_gray, bold: false }, + negative: { fg: UIColors.red.red, bold: true }, + } satisfies StyleCategory, +}; + +export const applyStyle = ( + text: string, + styleItem: StyleItem | StyleItemVersioned, + version = "default", +) => { + let style; + if (version && Object.keys(styleItem).includes(version)) { + style = (styleItem as StyleItemVersioned)[version]; + } else { + style = styleItem; + } + + let styled = text; + if (style.fg) { + styled = `{${style.fg}-fg}${styled}{/${style.fg}-fg}`; + } + if (style.bold) { + styled = `{bold}${styled}{/bold}`; + } + return styled; +}; + +export function applyStatusStyle(status: TaskRunStatusEnum, value?: string) { + const { fg, icon } = (UIConfig.status as any)[status]; + return applyStyle(`${icon} ${value ?? status}`, { ...UIConfig.labels.status, fg }); +} + +export function applyNumberStyle(count: number, inverse = false) { + let style; + if (count === 0) { + style = UIConfig.number.neutral; + } else if (count > 0) { + style = inverse ? UIConfig.number.negative : UIConfig.number.positive; + } else { + style = inverse ? UIConfig.number.positive : UIConfig.number.negative; + } + + return applyStyle(String(count), style); +} + +export function applyBooleanStyle( + value: boolean, + version?: typeof DEFAULT_VERSION | typeof BUSY_IDLE | typeof INVERSE_COLOR, +) { + const styleVersions = value ? UIConfig.boolean.TRUE : UIConfig.boolean.FALSE; + const style = styleVersions[version ?? DEFAULT_VERSION]; + return applyStyle(style.icon, { ...style }, version); +} + +export function applyAgentKindIdStyle(agentKindId: AgentKindId) { + const style = UIConfig.labels.agentTypeId; + return applyStyle(agentKindId.agentKind, clone(style)); +} + +export function applyAgentIdStyle(agentId: AgentId | AgentTypeId) { + const style = UIConfig.labels.agentId[agentId.agentKind as AgentKindEnum]; + const isAgentId = (agentId as AgentId).agentNum != null; + return applyStyle( + `${style.icon} ${agentId.agentType}${isAgentId ? `[${(agentId as AgentId).agentNum}]` : ""}`, + { ...style }, + ); +} + +export function applyToolsStyle(tools: AvailableTool[]) { + return tools + .map((t) => [ + applyToolNameStyle(t.name), + applyStyle(t.description, UIConfig.labels.description), + "", + ]) + .join("\n"); +} + +export function applyToolNameStyle(toolName: string) { + const style = UIConfig.labels.tool; + return applyStyle(`${style.icon} ${toolName}`, style); +} + +export function bool( + value: boolean, + version?: typeof DEFAULT_VERSION | typeof BUSY_IDLE | typeof INVERSE_COLOR, +) { + return applyBooleanStyle(value, version); +} + +export function num(value: number, inverse = false) { + return applyNumberStyle(value, inverse); +} + +export function label(value: string) { + return applyStyle(value, UIConfig.labels.default); +} + +export function agentConfigId(value: string) { + return label(value); +} + +export function versionAgentPoolStats(value: string, poolStats?: AgentConfigPoolStats) { + return `${applyStyle(value, UIConfig.labels.version)}${poolStats ? ` ${agentPoolStats(poolStats)}` : ""}`; +} + +export function versionNum(value: number) { + return applyStyle(`v${String(value)}`, UIConfig.labels.version); +} + +export function agentKindId(value: AgentKindId) { + return applyAgentKindIdStyle(value); +} +export function agentTypeId(value: AgentTypeId) { + return applyAgentIdStyle(value); +} +export function agentId(value: AgentId | AgentTypeId) { + return applyAgentIdStyle(value); +} +export function agentKind(value: AgentKindValue) { + return applyStyle(value, UIConfig.labels.agentKind); +} +export function agentType(value: AgentTypeValue) { + return applyStyle(value, UIConfig.labels.agentType); +} +export function desc(description: string) { + return applyStyle(description, UIConfig.labels.description); +} +export function tools(tools: AvailableTool[]) { + return applyToolsStyle(tools); +} + +export function timestamp(timestamp: string | Date) { + let value; + if (typeof timestamp === "string") { + value = new Date(timestamp).toLocaleString(); + } else { + value = timestamp.toLocaleString(); + } + + return applyStyle(value, UIConfig.labels.timestamp); +} + +export function eventType(event: string) { + return applyStyle(event, UIConfig.labels.eventType); +} +export function error(error: string) { + return applyStyle(error, UIConfig.labels.error); +} + +export function input(input: string, version?: typeof DEFAULT_VERSION | typeof AMBIENT_VERSION) { + const style = UIConfig.labels.input[version ?? DEFAULT_VERSION]; + return applyStyle(input, style); +} + +export function output(output: string) { + return applyStyle(output, UIConfig.labels.output); +} + +export function agentPoolStats(poolStats: AgentConfigPoolStats) { + return `[${num(poolStats.available)}/${num(poolStats.poolSize)}]`; +} + +export function agentPool(agentPool: { + agentType: string; + poolStats: AgentConfigPoolStats; + agentConfigVersion?: number; +}): string { + return `${agentId(stringToAgentType(agentPool.agentType))} ${(agentPool.agentConfigVersion && `${versionNum(agentPool.agentConfigVersion)} `) || ""}${agentPoolStats(agentPool.poolStats)}`; +} + +export function agent(agent: AgentInfo) { + return `${agentId(stringToAgent(agent.agentId))} ${versionNum(agent.agentConfigVersion)} ${bool(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`; +} + +export function applyTaskKindIdStyle(taskKindId: TaskKindId) { + const style = UIConfig.labels.taskTypeId; + return applyStyle(taskKindId.taskKind, clone(style)); +} + +export function applyTaskRunIdStyle(taskRunId: TaskRunId | TaskTypeId) { + const style = UIConfig.labels.taskRunId[taskRunId.taskKind as TaskKindEnum]; + const isTaskRunId = (taskRunId as TaskRunId).taskRunNum != null; + return applyStyle( + `${style.icon} ${taskRunId.taskType}${isTaskRunId ? `[${(taskRunId as TaskRunId).taskRunNum}]` : ""}`, + { ...style }, + ); +} + +export function taskKind(taskKind: TaskKindValue) { + return applyStyle(taskKind, UIConfig.labels.taskKind); +} + +export function taskType(taskType: TaskTypeValue) { + return applyStyle(taskType, UIConfig.labels.taskType); +} + +export function concurrencyMode(concurrencyMode: string) { + return applyStyle(concurrencyMode, UIConfig.labels.default); +} + +export function taskKindId(taskKindId: TaskKindId): string { + return applyTaskKindIdStyle(taskKindId); +} + +export function taskRunId(value: TaskRunId | TaskTypeId) { + return applyTaskRunIdStyle(value); +} + +export function taskPoolStats(poolStats: TaskConfigPoolStats) { + return `[${num(poolStats.active)}/${num(poolStats.poolSize)}]`; +} + +export function taskPool(taskPool: { + taskType: TaskTypeValue; + poolStats: TaskConfigPoolStats; + taskConfigVersion?: number; +}): string { + return `${taskRunId(stringToTaskType(taskPool.taskType))} ${(taskPool.taskConfigVersion && `${versionNum(taskPool.taskConfigVersion)} `) || ""}${taskPoolStats(taskPool.poolStats)}`; +} + +export function task(taskRunInfo: TaskRunInfo) { + return `${taskRunId(stringToTaskRun(taskRunInfo.taskRunId))} ${versionNum(taskRunInfo.taskConfigVersion)} ${applyStatusStyle(taskRunInfo.taskRun.status)}`; +} + +export function taskConfigId(value: TaskConfigIdValue) { + return label(value); +} + +export function versionTaskPoolStats(value: string, poolStats?: TaskConfigPoolStats) { + return `${applyStyle(value, UIConfig.labels.version)}${poolStats ? ` ${taskPoolStats(poolStats)}` : ""}`; +} + +export function taskRunStatus(status: TaskRunStatusEnum) { + return applyStatusStyle(status); +} diff --git a/src/ui/supervisor-monitor.ts b/src/ui/supervisor-monitor.ts new file mode 100644 index 0000000..3c39825 --- /dev/null +++ b/src/ui/supervisor-monitor.ts @@ -0,0 +1,41 @@ +import blessed from "blessed"; +import { TaskMonitor } from "./task-monitor/monitor.js"; +import { AgentMonitor } from "./agent-monitor/monitor.js"; + +const screen = blessed.screen({ + smartCSR: true, + title: "Supervisor UI", + debug: true, +}); + +new AgentMonitor({ + screen, + parent: blessed.box({ + parent: screen, + width: "50%", + height: "100%", + left: 0, + top: 0, + mouse: true, + keys: true, + vi: true, + border: { type: "line" }, + label: " Agent Monitor ", + }), +}).start(); + +new TaskMonitor({ + screen, + parent: blessed.box({ + parent: screen, + width: "50%", + height: "100%", + left: "50%", + top: 0, + mouse: true, + keys: true, + vi: true, + border: { type: "line" }, + label: " Task Monitor ", + }), +}).start(); diff --git a/src/ui/task-monitor.ts b/src/ui/task-monitor.ts deleted file mode 100644 index 5a141e5..0000000 --- a/src/ui/task-monitor.ts +++ /dev/null @@ -1,472 +0,0 @@ -import blessed from "blessed"; -import chokidar from "chokidar"; -import { createReadStream } from "fs"; -import { join } from "path"; -import { createInterface } from "readline"; -import { stringToAgentId } from "src/agents/agent-id.js"; -import { - TaskConfig, - TaskHistoryEntry, - TaskStatus, - TaskStatusEnum, -} from "src/tasks/task-manager.js"; -import { TaskUpdate, TaskUpdateTypeEnum } from "src/tasks/task-state-logger.js"; -import { truncateText } from "src/utils/text.js"; -import { formatDuration } from "src/utils/time.js"; -import { - AMBIENT_VERSION, - applyAgentIdStyle, - applyBooleanStyle, - applyNumberStyle, - applyStatusStyle, - applyStyle, - UIConfig, -} from "./ui-config.js"; -import { LogInit } from "src/base/audit-log.js"; - -const TASK_CONFIG_DEFAULT_TEXT = "Select a task to view config"; -const TASK_DETAILS_DEFAULT_TEXT = "Select a task to view details"; -const TASK_HISTORY_DEFAULT_TEXT = "Select a task to view history"; - -class TaskMonitor { - private screen: blessed.Widgets.Screen; - private taskList: blessed.Widgets.ListElement; - private taskConfig: blessed.Widgets.BoxElement; - private taskDetails: blessed.Widgets.BoxElement; - private taskHistory: blessed.Widgets.BoxElement; - private logBox: blessed.Widgets.Log; - private tasks = new Map(); - private taskConfigs = new Map(); - private selectedTaskIndex: number | null = null; - - constructor() { - this.screen = blessed.screen({ - smartCSR: true, - title: "Task Registry Monitor", - debug: true, - }); - - this.taskList = blessed.list({ - parent: this.screen, - width: "20%", - height: "70%", - left: 0, - top: 0, - border: { type: "line" }, - label: " Tasks ", - keys: true, - vi: true, - mouse: true, - style: UIConfig.list, - tags: true, - scrollable: true, - scrollbar: UIConfig.scrollbar, - }); - - this.taskConfig = blessed.box({ - parent: this.screen, - width: "20%", - height: "70%", - left: "20%", - top: 0, - border: { type: "line" }, - label: " Config ", - content: TASK_CONFIG_DEFAULT_TEXT, - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - alwaysScroll: true, - scrollbar: UIConfig.scrollbar, - }); - - this.taskDetails = blessed.box({ - parent: this.screen, - width: "60%", - height: "50%", - right: 0, - top: 0, - border: { type: "line" }, - label: " Details ", - content: TASK_DETAILS_DEFAULT_TEXT, - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - alwaysScroll: true, - scrollbar: UIConfig.scrollbar, - }); - - this.taskHistory = blessed.box({ - parent: this.screen, - width: "60%", - height: "20%", - right: 0, - top: "50%", - border: { type: "line" }, - label: " History ", - content: TASK_HISTORY_DEFAULT_TEXT, - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - alwaysScroll: true, - scrollbar: UIConfig.scrollbar, - }); - - this.logBox = blessed.log({ - parent: this.screen, - width: "100%", - height: "30%", - left: 0, - bottom: 0, - border: { type: "line" }, - label: " Live Updates ", - tags: true, - scrollable: true, - mouse: true, - keys: true, - vi: true, - scrollbar: UIConfig.scrollbar, - }); - - this.setupEventHandlers(); - this.screen.render(); - } - - private setupEventHandlers() { - this.screen.key(["escape", "q", "C-c"], () => process.exit(0)); - - const self = this; - // Handle task selection - this.taskList.on("select", (_, selectedIndex) => { - const item = this.taskList.getItem(selectedIndex); - self.selectedTaskIndex = selectedIndex; - if (item?.content) { - // Remove color tags to get clean taskId - const taskId = - item.content - .toString() - .replace(/\{[^}]+\}/g, "") - .split(" ") - .at(-1) ?? ""; - // this.logBox.log(`Selected task: ${taskId}`); - this.updateTaskDetails(taskId, false); - this.updateTaskConfig(taskId, false); - this.screen.render(); - } - }); - - // Enable mouse scrolling - this.taskList.on("mouse", (data) => { - if (data.action === "wheelup") { - this.taskList.scroll(-1); - this.screen.render(); - } else if (data.action === "wheeldown") { - this.taskList.scroll(1); - this.screen.render(); - } - }); - - this.taskDetails.on("mouse", (data) => { - if (data.action === "wheelup") { - this.taskDetails.scroll(-1); - this.screen.render(); - } else if (data.action === "wheeldown") { - this.taskDetails.scroll(1); - this.screen.render(); - } - }); - } - - private reset(shouldRender = true) { - // Reset data - [this.tasks, this.taskConfigs].forEach((m) => m.clear()); - this.selectedTaskIndex = null; - - // Update content - this.updateTaskList(false); - this.updateTaskDetails(undefined, false); - this.updateTaskConfig(undefined, false); - - // Reset log - this.logBox.setContent(""); - this.logBox.log("Reading initial state from log..."); - - // Render - if (shouldRender) { - this.screen.render(); - } - } - - private updateTaskList(shouldRender = true): void { - const items = Array.from(this.tasks.values()).map( - (task) => `${applyStatusStyle(task.status, task.id)}`, - ); - - this.taskList.setItems(items); - if (shouldRender) { - this.screen.render(); - } - } - - private updateTaskDetails(taskId?: string, shouldRender = true): void { - if (!taskId) { - this.taskDetails.setContent(TASK_DETAILS_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - return; - } - - const task = this.tasks.get(taskId); - if (!task) { - this.taskDetails.setContent(TASK_DETAILS_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - return; - } - - const details = [ - `{bold}Status:{/bold} ${applyStatusStyle(task.status)}{/}`, - `{bold}Is Occupied:{/bold} ${applyBooleanStyle(task.isOccupied)}`, - task.currentAgentId - ? `{bold}Current Agent:{/bold} ${applyAgentIdStyle(stringToAgentId(task.currentAgentId))}` - : null, - `{bold}Owner:{/bold} ${applyAgentIdStyle(stringToAgentId(task.ownerAgentId))}`, - `{bold}Completed Runs:{/bold} ${applyNumberStyle(task.completedRuns)}`, - `{bold}Error Count:{/bold} ${applyNumberStyle(task.errorCount, true)}`, - task.lastRunAt - ? `{bold}Last Run:{/bold} ${applyStyle(new Date(task.lastRunAt).toLocaleString(), UIConfig.labels.timestamp)}` - : null, - task.nextRunAt - ? `{bold}Next Run:{/bold} ${applyStyle(new Date(task.nextRunAt).toLocaleString(), UIConfig.labels.timestamp)}` - : null, - "", - task.history.at(-1)?.output - ? `{bold}Output:{/bold}\n${applyStyle(String(task.history.at(-1)?.output), UIConfig.labels.output)}` - : null, - "", - "", - task.history.at(-1)?.error - ? `{bold}Error:{/bold}\n${applyStyle(String(task.history.at(-1)?.error), UIConfig.labels.error)}` - : null, - "", - ] - .filter((line) => line !== null) - .join("\n"); - - this.taskDetails.setContent(details); - - const history = [ - ...task.history - .slice(-30) - .map( - (entry) => - `${applyStyle(new Date(entry.timestamp).toLocaleString(), UIConfig.labels.timestamp)}` + - ` ${applyStatusStyle(entry.terminalStatus)}` + - (entry.agentId && entry.agentId !== "null" - ? ` ${applyAgentIdStyle(stringToAgentId(String(entry.agentId)))}` - : "") + - ` ${applyStyle(formatDuration(entry.executionTimeMs), UIConfig.labels.executionTime)}` + - (entry.output - ? ` ${applyStyle(truncateText(String(entry.output), 512), UIConfig.labels.output, AMBIENT_VERSION)}` - : "") + - (entry.error - ? ` ${applyStyle(truncateText(entry.error, 512), UIConfig.labels.error)}` - : ""), - ), - ] - .filter((line) => line !== null) - .join("\n"); - this.taskHistory.setContent(history); - - // this.logBox.log(`Updated details for task: ${taskId}`); - if (shouldRender) { - this.screen.render(); - } - } - - private updateTaskConfig(taskId?: string, shouldRender = true): void { - if (!taskId) { - this.taskConfig.setContent(TASK_CONFIG_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - return; - } - - const config = this.taskConfigs.get(taskId); - if (!config) { - this.taskConfig.setContent(TASK_CONFIG_DEFAULT_TEXT); - if (shouldRender) { - this.screen.render(); - } - return; - } - - const details = [ - `{bold}Task ID:{/bold} ${applyStyle(config.id, UIConfig.labels.taskId)}`, - `{bold}Agent Kind:{/bold} ${applyStyle(config.agentKind, UIConfig.labels.agentKind)}`, - `{bold}Agent Type:{/bold} ${applyStyle(config.agentType, UIConfig.labels.agentType)}`, - `{bold}Owner:{/bold} ${applyAgentIdStyle(stringToAgentId(config.ownerAgentId))}`, - "", - "{bold}Execution Settings:{/bold}", - `{bold}Interval:{/bold} ${applyStyle(formatDuration(config.intervalMs), UIConfig.labels.timestamp)}`, - `{bold}Run Immediately:{/bold} ${applyBooleanStyle(config.runImmediately)}`, - config.maxRuns - ? `{bold}Max Runs:{/bold} ${applyStyle(String(config.maxRuns), UIConfig.labels.timestamp)}` - : null, - config.maxRetries - ? `{bold}Max Retries:{/bold} ${applyStyle(String(config.maxRetries), UIConfig.labels.timestamp)}` - : null, - config.retryDelayMs - ? `{bold}Retry Delay:{/bold} ${applyStyle(formatDuration(config.retryDelayMs), UIConfig.labels.timestamp)}` - : null, - "", - "{bold}Description:{/bold}", - applyStyle(config.description, UIConfig.labels.description), - "", - "{bold}Input:{/bold}", - applyStyle(config.input, UIConfig.labels.input), - ] - .filter((line) => line !== null) - .join("\n"); - - this.taskConfig.setContent(details); - if (shouldRender) { - this.screen.render(); - } - } - - private processLogLine(line: string, shouldRender = true): void { - try { - const update: TaskUpdate | LogInit = JSON.parse(line); - if (update.type === "@log_init") { - // RESET - this.reset(shouldRender); - return; - } - - const task = this.tasks.get(update.taskId) || { - id: update.taskId, - status: "SCHEDULED" as TaskStatusEnum, - isOccupied: false, - errorCount: 0, - currentRetryAttempt: 0, - ownerAgentId: "", - completedRuns: 0, - history: [], - }; - - // if(update.type === '') - - if (update.type === TaskUpdateTypeEnum.CONFIG) { - this.taskConfigs.set(update.taskId, update.data as TaskConfig); - } else if (update.type === TaskUpdateTypeEnum.STATUS) { - Object.assign(task, update.data); - } else if (update.type === TaskUpdateTypeEnum.HISTORY) { - task.history.push(update.data as TaskHistoryEntry); - } - - this.tasks.set(update.taskId, task); - this.updateTaskList(false); - - // Update details if this task is currently selected - const selectedIndex = this.selectedTaskIndex; - if (typeof selectedIndex === "number") { - const selectedItem = this.taskList.getItem(selectedIndex); - if (selectedItem?.content) { - const selectedTaskId = selectedItem.content.toString().replace(/\{[^}]+\}/g, ""); - if (selectedTaskId === update.taskId) { - this.updateTaskDetails(update.taskId, false); - } - } - } - - this.logBox.log( - `${new Date().toLocaleString()} - Task ${update.taskId}: ${update.type} update ${JSON.stringify(update.data)}`, - ); - if (shouldRender) { - this.screen.render(); - } - } catch (error) { - if (error instanceof Error) { - this.logBox.log(`Error processing log line: ${error.message}`); - } else { - this.logBox.log("Unknown error processing log line"); - } - } - } - - private async initializeStateFromLog(logPath: string): Promise { - try { - this.logBox.log("Reading initial state from log..."); - - const rl = createInterface({ - input: createReadStream(logPath, { encoding: "utf8" }), - crlfDelay: Infinity, - }); - - for await (const line of rl) { - this.processLogLine(line); // Don't render updates while initializing - } - - this.logBox.log(`Initial state loaded: ${this.tasks.size} tasks found`); - this.updateTaskList(); - } catch (error) { - if (error instanceof Error) { - this.logBox.log(`Error reading initial state: ${error.message}`); - } else { - this.logBox.log("Unknown error reading initial state"); - } - } - } - - private watchLogFile(logPath: string): void { - let lastProcessedSize = 0; - - chokidar - .watch(logPath, { - persistent: true, - usePolling: true, - interval: 100, - }) - .on("change", async (path) => { - try { - const rl = createInterface({ - input: createReadStream(path, { encoding: "utf8", start: lastProcessedSize }), - crlfDelay: Infinity, - }); - - for await (const line of rl) { - this.processLogLine(line); // Render updates for new changes - lastProcessedSize += Buffer.from(line).length + 1; // +1 for newline - } - this.screen.render(); - } catch (error) { - if (error instanceof Error) { - this.logBox.log(`Error processing log update: ${error.message}`); - } - } - }); - } - - public async start(): Promise { - const logPath = join(process.cwd(), "logs", "task_state.log"); - - // First read the entire log to build initial state - await this.initializeStateFromLog(logPath); - - // Then start watching for changes - this.watchLogFile(logPath); - } -} - -// Start the monitor -const monitor = new TaskMonitor(); -monitor.start(); diff --git a/src/ui/task-monitor/monitor.ts b/src/ui/task-monitor/monitor.ts new file mode 100644 index 0000000..1001ffc --- /dev/null +++ b/src/ui/task-monitor/monitor.ts @@ -0,0 +1,554 @@ +import blessed from "blessed"; +import { join } from "path"; +import { clone } from "remeda"; +import { stringToAgent } from "src/agents/agent-id.js"; +import { isTaskRunActiveStatus, TaskConfig, TaskKindEnumSchema } from "src/tasks/manager/dto.js"; +import { StateUpdateType, TaskRunInfo, TaskStateBuilder } from "src/tasks/state/builder.js"; +import { + stringToTaskConfig, + stringToTaskKind, + stringToTaskRun, + TaskConfigId, + TaskKindId, + taskSomeIdToTypeValue, + TaskTypeId, +} from "src/tasks/task-id.js"; +import { BaseMonitor, ParentInput, ScreenInput } from "../base/monitor.js"; +import * as st from "../config.js"; + +const TASK_CONFIG_DETAIL_DEFAULT_TEXT = "Select task pool to view task config detail"; +const TASK_VERSION_DEFAULT_TEXT = "Select pool to view versions"; +const TASK_RUN_LIST_DEFAULT_TEXT = "Select pool to view tasks"; +const TASK_RUN_DETAIL_DEFAULT_TEXT = "Select task run to view task run detail"; +// const TASK_LIFECYCLE_HISTORY_DEFAULT_TEXT = "Select task run to view lifecycle events"; + +export class TaskMonitor extends BaseMonitor { + private stateBuilder: TaskStateBuilder; + private taskPoolList: blessed.Widgets.ListElement; + private taskPoolListItemsData: { + taskTypeId: TaskTypeId | TaskKindId; + itemContent: string; + }[] = []; + private taskPoolListSelectedIndex: number | null = null; + + private taskVersionsList: blessed.Widgets.ListElement; + private taskVersionsListItemsData: { + taskTypeId: TaskConfigId; + itemContent: string; + }[] = []; + private taskVersionsListSelectedIndex: number | null = null; + + private taskRunList: blessed.Widgets.ListElement; + private taskRunListItemsData: { + taskRun: TaskRunInfo; + itemContent: string; + }[] = []; + private taskRunListSelectedIndex: number | null = null; + + private taskConfigDetail: blessed.Widgets.BoxElement; + private taskRunDetail: blessed.Widgets.BoxElement; + private logBox: blessed.Widgets.Log; + + constructor(arg: ParentInput | ScreenInput) { + super(arg); + this.stateBuilder = new TaskStateBuilder(); + this.stateBuilder.on("log:reset", () => { + this.reset(); + }); + this.stateBuilder.on("log:new_line", (line) => { + this.logBox.log(`${new Date().toLocaleString()} - ${line}`); + }); + this.stateBuilder.on("state:updated", (update) => { + switch (update.type) { + case StateUpdateType.TASK_CONFIG: + case StateUpdateType.POOL: + this.updateTaskPoolsList(false); + break; + case StateUpdateType.TASK_RUN: + this.updateTaskVersionsList(false); + break; + case StateUpdateType.HISTORY_ENTRY: + this.updateTaskRunDetails(undefined, false); + break; + // case StateUpdateType.AGENT: + // this.updateAgentVersionsList(false); + // this.updateAgentDetails(); + // break; + // case StateUpdateType.ASSIGNMENT: + // this.updateAgentDetails(); + // break; + // case StateUpdateType.FULL: + // // Full refresh + // // this.reset(); + // break; + } + }); + + this.stateBuilder.on("error", (error: Error) => { + // OK + console.error("Error occurred:", error); + }); + + // Left column - Pools and Task Runs (70%) + this.taskPoolList = blessed.list({ + parent: this.parent, + width: "30%", + height: "20%", + left: 0, + top: 0, + border: { type: "line" }, + label: " Task Pools ", + style: st.UIConfig.list, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + this.taskVersionsList = blessed.list({ + parent: this.parent, + width: "30%", + height: "20%", + left: 0, + top: "20%", + border: { type: "line" }, + label: " Task Runs Versions ", + content: TASK_VERSION_DEFAULT_TEXT, + style: st.UIConfig.list, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + this.taskRunList = blessed.list({ + parent: this.parent, + width: "30%", + height: "30%", + left: 0, + top: "40%", + border: { type: "line" }, + label: " Task Runs ", + content: TASK_RUN_LIST_DEFAULT_TEXT, + style: st.UIConfig.list, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + // Center column - Details and Tools (40%) + this.taskConfigDetail = blessed.box({ + parent: this.parent, + width: "40%", + height: "20%", + left: "30%", + top: 0, + border: { type: "line" }, + label: " Task Config ", + content: TASK_CONFIG_DETAIL_DEFAULT_TEXT, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + this.taskRunDetail = blessed.box({ + parent: this.parent, + width: "40%", + height: "50%", + left: "30%", + top: "20%", + border: { type: "line" }, + label: " Task Run Detail ", + content: TASK_RUN_DETAIL_DEFAULT_TEXT, + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + // Bottom - Live Updates + this.logBox = blessed.log({ + parent: this.parent, + width: "100%", + height: "30%", + left: 0, + bottom: 0, + border: { type: "line" }, + label: " Live Updates ", + tags: true, + scrollable: true, + mouse: true, + keys: true, + vi: true, + scrollbar: st.UIConfig.scrollbar, + }); + + this.setupEventHandlers(); + this.screen.render(); + } + + private setupEventHandlers() { + this.screen.key(["escape", "q", "C-c"], () => process.exit(0)); + + this.taskPoolList.on("select", (_, selectedIndex) => { + this.taskPoolListSelectedIndex = selectedIndex; + const itemData = this.taskPoolListItemsData[this.taskPoolListSelectedIndex]; + if (!itemData) { + throw new Error( + `Missing data for selected pool on index:${this.taskPoolListSelectedIndex}`, + ); + } + let taskConfig; + const taskTypeId = itemData.taskTypeId; + if ((taskTypeId as TaskTypeId).taskType) { + taskConfig = this.stateBuilder.getTaskConfig( + taskSomeIdToTypeValue(taskTypeId as TaskTypeId), + ); + } + + this.updateTaskConfig(taskConfig, false); + this.updateTaskVersionsList(); + }); + + this.taskVersionsList.on("select", (_, selectedIndex) => { + this.taskVersionsListSelectedIndex = selectedIndex; + const itemData = this.taskVersionsListItemsData[this.taskVersionsListSelectedIndex]; + if (!itemData) { + throw new Error(`Missing data for selectedIndex:${this.taskVersionsListSelectedIndex}`); + } + + const taskConfigId = itemData.taskTypeId as TaskConfig; + const taskConfig = this.stateBuilder.getTaskConfig( + taskSomeIdToTypeValue(taskConfigId), + taskConfigId.taskConfigVersion, + ); + + this.updateTaskConfig(taskConfig, false); + this.updateTaskVersionsList(); + }); + + this.taskRunList.on("select", (_, selectedIndex) => { + this.taskRunListSelectedIndex = selectedIndex; + const itemData = this.taskRunListItemsData[this.taskRunListSelectedIndex]; + if (!itemData) { + throw new Error(`Missing data for selectedIndex:${this.taskPoolListSelectedIndex}`); + } + const { taskRun } = itemData; + const taskRunConfigId = stringToTaskConfig(taskRun.taskConfigId); + const taskRunTypeId = taskSomeIdToTypeValue(taskRunConfigId); + const taskRunConfig = this.stateBuilder.getTaskConfig( + taskRunTypeId, + taskRunConfigId.taskConfigVersion, + ); + this.updateTaskConfig(taskRunConfig, false); + this.updateTaskRunDetails(itemData.taskRun); + }); + + // Mouse scrolling for all components + [ + this.taskPoolList, + this.taskRunList, + this.taskConfigDetail, + this.taskRunDetail, + // this.lifecycleHistory, + ].forEach((component) => { + component.on("mouse", (data) => { + if (data.action === "wheelup") { + component.scroll(-1); + this.screen.render(); + } else if (data.action === "wheeldown") { + component.scroll(1); + this.screen.render(); + } + }); + }); + } + + updateTaskPoolsList(shouldRender = true): void { + this.taskPoolListItemsData.splice(0); + const state = this.stateBuilder.getState(); + Array.from(state.taskRunPools.entries()) + .sort(([a], [b]) => { + // Sort task kind + const aPoolId = stringToTaskKind(a); + const bPoolId = stringToTaskKind(b); + const aSuper = aPoolId.taskKind === TaskKindEnumSchema.Values.supervisor; + const bSuper = bPoolId.taskKind === TaskKindEnumSchema.Values.supervisor; + if (aSuper && !bSuper) { + return -1; + } else if (!aSuper && bSuper) { + return 1; + } else { + return aPoolId.taskKind.localeCompare(bPoolId.taskKind); + } + }) + .forEach(([taskKindStr, taskTypePools]) => { + const taskKindId = stringToTaskKind(taskKindStr); + this.taskPoolListItemsData.push({ + taskTypeId: taskKindId, + itemContent: st.taskKindId(taskKindId), + }); + Array.from(taskTypePools.entries()) + .sort(([a], [b]) => { + // Sort task type + return a.localeCompare(b); + }) + .forEach(([taskTypeStr, taskPool]) => { + this.taskPoolListItemsData.push({ + taskTypeId: { taskKind: taskKindId.taskKind, taskType: taskTypeStr }, + itemContent: st.taskPool(taskPool), + }); + }); + }); + + if (this.taskPoolListSelectedIndex == null && this.taskPoolListItemsData.length) { + this.taskPoolListSelectedIndex = 0; + this.taskPoolList.select(this.taskPoolListSelectedIndex); + } + this.taskPoolList.setItems(this.taskPoolListItemsData.map((it) => it.itemContent)); + + this.updateTaskVersionsList(false); + if (shouldRender) { + this.screen.render(); + } + } + + private updateTaskVersionsList(shouldRender = true): void { + this.taskVersionsListItemsData.splice(0); + + if (this.taskPoolListSelectedIndex != null) { + // Get versions of selected task pool + const itemData = this.taskPoolListItemsData[this.taskPoolListSelectedIndex]; + if (!itemData) { + throw new Error( + `Missing data for selected pool on index:${this.taskPoolListSelectedIndex}`, + ); + } + + const taskPoolTypeId = itemData.taskTypeId as TaskTypeId; + if (taskPoolTypeId.taskType != null) { + // List versions + const taskPool = this.stateBuilder.getTaskPool( + taskPoolTypeId.taskKind, + taskPoolTypeId.taskType, + ); + if (taskPool) { + const hasMultipleVersions = taskPool.versions.length > 1; + if (hasMultipleVersions) { + this.taskPoolListItemsData.push({ + taskTypeId: { + taskKind: taskPoolTypeId.taskKind, + taskType: taskPoolTypeId.taskType, + }, + itemContent: st.versionAgentPoolStats("all"), + }); + } + + clone(taskPool.versions) + .reverse() + .forEach(([taskConfigVersion, poolStats]) => { + const taskTypeId = { + taskKind: taskPoolTypeId.taskKind, + taskType: taskPoolTypeId.taskType, + taskConfigVersion, + }; + this.taskVersionsListItemsData.push({ + taskTypeId, + itemContent: st.versionTaskPoolStats(st.versionNum(taskConfigVersion), poolStats), + }); + }); + } + } + } + + this.taskVersionsList.setItems(this.taskVersionsListItemsData.map((it) => it.itemContent)); + this.taskVersionsList.setContent( + this.taskVersionsListItemsData.length ? "" : TASK_VERSION_DEFAULT_TEXT, + ); + this.updateTaskList(false); + if (shouldRender) { + this.screen.render(); + } + } + + private updateTaskList(shouldRender = true): void { + this.taskRunListItemsData.splice(0); + if (this.taskVersionsListSelectedIndex != null) { + Array.from(this.stateBuilder.getAllTaskRuns()) + .filter((a) => { + if (this.taskVersionsListSelectedIndex == null) { + return false; + } + + const taskRunId = stringToTaskRun(a.taskRunId); + const taskRunPoolListItem = + this.taskVersionsListItemsData[this.taskVersionsListSelectedIndex]; + if ( + taskRunPoolListItem && + taskRunPoolListItem.taskTypeId.taskKind === taskRunId.taskKind + ) { + if ((taskRunPoolListItem.taskTypeId as TaskTypeId).taskType != null) { + if ((taskRunPoolListItem.taskTypeId as TaskTypeId).taskType == taskRunId.taskType) { + if ((taskRunPoolListItem.taskTypeId as TaskConfigId).taskConfigVersion != null) { + return ( + (taskRunPoolListItem.taskTypeId as TaskConfigId).taskConfigVersion === + taskRunId.taskConfigVersion + ); + } + } else { + return false; + } + } + return true; + } + return false; + }) + .sort((a, b) => { + const aTaskRunId = stringToTaskRun(a.taskRunId); + const bTaskRunId = stringToTaskRun(b.taskRunId); + + if (aTaskRunId.taskConfigVersion != bTaskRunId.taskConfigVersion) { + return Math.sign(aTaskRunId.taskConfigVersion - bTaskRunId.taskConfigVersion); + } + + const comp = aTaskRunId.taskType.localeCompare(bTaskRunId.taskType); + if (comp === 0) { + return Math.sign(aTaskRunId.taskRunNum - bTaskRunId.taskRunNum); + } else { + return comp; + } + }) + .forEach((task) => { + this.taskRunListItemsData.push({ + taskRun: task, + itemContent: st.task(task), + }); + }); + } + + this.taskRunList.setItems(this.taskRunListItemsData.map((it) => it.itemContent)); + this.taskRunList.setContent(this.taskRunListItemsData.length ? "" : TASK_RUN_LIST_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + } + + private updateTaskConfig(taskConfig?: TaskConfig, shouldRender = true): void { + if (!taskConfig) { + this.taskConfigDetail.setContent(TASK_CONFIG_DETAIL_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + + const details = [ + `${st.label("Id")}: ${st.taskConfigId(taskConfig.taskConfigId)}`, + `${st.label("Version")}: ${st.versionNum(taskConfig.taskConfigVersion)}`, + `${st.label("Task Kind")}: ${st.taskKind(taskConfig.taskKind)}`, + `${st.label("Task Type")}: ${st.taskType(taskConfig.taskType)}`, + `${st.label("Concurrency Mode")}: ${st.concurrencyMode(taskConfig.concurrencyMode)}`, + "", + `${st.label("Description")}: `, + st.desc(taskConfig.description), + "", + `${st.label("Input")}: `, + st.input(taskConfig.taskConfigInput, "ambient"), + ].join("\n"); + this.taskConfigDetail.setContent(details); + if (shouldRender) { + this.screen.render(); + } + } + + private updateTaskRunDetails(taskRunInfo?: TaskRunInfo, shouldRender = true): void { + if (!taskRunInfo) { + this.taskRunDetail.setContent(TASK_RUN_DETAIL_DEFAULT_TEXT); + if (shouldRender) { + this.screen.render(); + } + return; + } + + const { + taskRunId, + status, + isOccupied, + currentAgentId, + ownerAgentId, + completedRuns, + errorCount, + lastRunAt, + nextRunAt, + history, + taskRunInput, + } = taskRunInfo.taskRun; + + const { isDestroyed } = taskRunInfo; + + const details = [ + `${st.label("Id")}: ${st.taskRunId(stringToTaskRun(taskRunId))}`, + `${st.label("In Use")}: ${st.bool(isTaskRunActiveStatus(status), "busy_idle")}`, + `${st.label("Status")}: ${st.taskRunStatus(status)}`, + `${st.label("Is Destroyed")}: ${st.bool(isDestroyed, "inverse_color")}`, + `${st.label("Is Occupied")}: ${st.bool(isOccupied)}${currentAgentId ? st.agentId(stringToAgent(currentAgentId)) : ""}`, + `${st.label("Owner")}: ${st.agentId(stringToAgent(ownerAgentId))}`, + `${st.label("Completed Runs")}: ${st.num(completedRuns)}`, + `${st.label("Error Count")}: ${st.num(errorCount, true)}`, + lastRunAt ? `${st.label("Last Run")}: ${st.timestamp(lastRunAt)}` : null, + nextRunAt ? `${st.label("Next Run")}: ${st.timestamp(nextRunAt)}` : null, + "", + `${st.label("Input")}:`, + `${st.input(taskRunInput)}`, + "", + history.at(-1)?.output + ? `${st.label("Output")}: \n${st.output(String(history.at(-1)?.output))}` + : null, + "", + "", + history.at(-1)?.error + ? `${st.label("Error")}: \n${st.error(String(history.at(-1)?.error))}` + : null, + "", + ].join("\n"); + this.taskRunDetail.setContent(details); + if (shouldRender) { + this.screen.render(); + } + } + + reset(shouldRender = true): void { + // Reset selections + this.taskPoolListSelectedIndex = null; + + // Update content + this.updateTaskPoolsList(false); + + // Reset log box + this.logBox.setContent(""); + this.logBox.log("Reading initial state from log..."); + + // Render + if (shouldRender) { + this.screen.render(); + } + } + + public async start(): Promise { + const logPath = join(process.cwd(), "logs", "task_state.log"); + // First read the entire log to build initial state + await this.stateBuilder.watchLogFile(logPath); + } +} diff --git a/src/ui/ui-config.ts b/src/ui/ui-config.ts deleted file mode 100644 index 509f2cb..0000000 --- a/src/ui/ui-config.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { clone } from "remeda"; -import { AgentId, AgentPoolId, AgentPoolTypeId } from "src/agents/agent-id.js"; -import { AgentKind, AgentKindSchema, AvailableTool } from "src/agents/agent-registry.js"; -import { TaskStatusEnum } from "src/tasks/task-manager.js"; -import { Agent, AgentPool } from "./agent-monitor.js"; - -export interface StyleItem { - fg?: string; - bold?: boolean; - - italic?: boolean; - icon?: string; -} -export type StyleItemVersioned = Record; -export type StyleItemValue = StyleItem | StyleItemVersioned; - -export type StyleCategory = Record; - -export const DEFAULT_VERSION = "default"; -export const BUSY_IDLE = "busy_idle"; -export const INVERSE_COLOR = "inverse_color"; -export const AMBIENT_VERSION = "ambient"; - -export const UIConfig = { - labels: { - default: { fg: "white", bold: true }, - taskId: { fg: "#CC5500", bold: true }, - status: { fg: "white", bold: true }, - agentKind: { fg: "magenta", bold: true }, - agentType: { fg: "cyan", bold: true }, - agentId: { - [AgentKindSchema.Values.supervisor]: { fg: "#8B4513", bold: true, icon: "⬢" }, - [AgentKindSchema.Values.operator]: { fg: "#8B4513", bold: true, icon: "⬡" }, - }, - agentPoolId: { fg: "white", italic: true }, - owner: { fg: "#8B4513", bold: true }, - description: { fg: "#7393B3", bold: false }, - input: { fg: "yellow", bold: false }, - output: { - [DEFAULT_VERSION]: { fg: "green", bold: false }, - [AMBIENT_VERSION]: { fg: "#2E8B57", bold: false }, - }, - error: { fg: "red", bold: true }, - executionTime: { fg: "yellow" }, - timestamp: { fg: "gray" }, - tool: { fg: "cyan", icon: "⚒" }, - eventType: { - fg: "yellow", - bold: true, - icon: "⚡", - }, - } satisfies StyleCategory, - - status: { - RUNNING: { fg: "green", icon: "▶" }, // Play triangle - FAILED: { fg: "red", icon: "■" }, // Square - COMPLETED: { fg: "blue", icon: "●" }, // Circle - SCHEDULED: { fg: "yellow", icon: "◆" }, // Diamond - WAITING: { fg: "cyan", icon: "◇" }, // Hollow diamond - STOPPED: { fg: "grey", icon: "◼" }, // Filled square - REMOVED: { fg: "#71797E", icon: "×" }, // Cross - } satisfies StyleCategory, - - BUSY: { - fg: "red", - bg: null, - bold: true, - prefix: "⚡", - suffix: "", - }, - IDLE: { - fg: "green", - bg: null, - bold: false, - prefix: "○", - suffix: "", - }, - - boolean: { - TRUE: { - [DEFAULT_VERSION]: { fg: "green", icon: "[✓]" }, - [INVERSE_COLOR]: { fg: "red", icon: "[✓]" }, - [BUSY_IDLE]: { - fg: "red", - bold: true, - icon: "⚡", - }, - }, - FALSE: { - [DEFAULT_VERSION]: { fg: "red", icon: "[✕]" }, - [INVERSE_COLOR]: { fg: "green", icon: "[✕]" }, - [BUSY_IDLE]: { - fg: "green", - bold: false, - icon: "○", - }, - }, - } satisfies StyleCategory, - - borders: { - type: "line", - fg: "white", - }, - - list: { - selected: { bg: "blue", fg: "white" }, - border: { fg: "white" }, - item: { - hover: { bg: "blue" }, - }, - }, - - scrollbar: { - ch: " ", - track: { bg: "gray" }, - style: { inverse: true }, - }, - number: { - positive: { fg: "green", bold: true }, - neutral: { fg: "grey", bold: false }, - negative: { fg: "red", bold: true }, - } satisfies StyleCategory, -}; - -export const applyStyle = ( - text: string, - styleItem: StyleItem | StyleItemVersioned, - version = "default", -) => { - let style; - if (version && Object.keys(styleItem).includes(version)) { - style = (styleItem as StyleItemVersioned)[version]; - } else { - style = styleItem; - } - - let styled = text; - if (style.fg) { - styled = `{${style.fg}-fg}${styled}{/${style.fg}-fg}`; - } - if (style.bold) { - styled = `{bold}${styled}{/bold}`; - } - return styled; -}; - -export function applyStatusStyle(status: TaskStatusEnum, value?: string) { - const { fg, icon } = UIConfig.status[status]; - return applyStyle(`${icon} ${value ?? status}`, { ...UIConfig.labels.status, fg }); -} - -export function applyNumberStyle(count: number, inverse = false) { - let style; - if (count === 0) { - style = UIConfig.number.neutral; - } else if (count > 0) { - style = inverse ? UIConfig.number.negative : UIConfig.number.positive; - } else { - style = inverse ? UIConfig.number.positive : UIConfig.number.negative; - } - - return applyStyle(String(count), style); -} - -export function applyBooleanStyle( - value: boolean, - version?: typeof DEFAULT_VERSION | typeof BUSY_IDLE | typeof INVERSE_COLOR, -) { - const styleVersions = value ? UIConfig.boolean.TRUE : UIConfig.boolean.FALSE; - const style = styleVersions[version ?? DEFAULT_VERSION]; - return applyStyle(style.icon, { ...style }, version); -} - -export function applyAgentPoolIdStyle(agentPoolId: AgentPoolId) { - const style = UIConfig.labels.agentPoolId; - return applyStyle(agentPoolId.agentKind, clone(style)); -} - -export function applyAgentIdStyle(agentId: AgentId | AgentPoolTypeId) { - const style = UIConfig.labels.agentId[agentId.agentKind as AgentKind]; - const isAgentId = (agentId as AgentId).num != null; - return applyStyle( - `${style.icon} ${agentId.agentType}${isAgentId ? `[${(agentId as AgentId).num}]` : ""}`, - { ...style }, - ); -} - -export function applyToolsStyle(tools: AvailableTool[]) { - return tools - .map((t) => [ - applyToolNameStyle(t.name), - applyStyle(t.description, UIConfig.labels.description), - "", - ]) - .join("\n"); -} - -export function applyToolNameStyle(toolName: string) { - const style = UIConfig.labels.tool; - return applyStyle(`${style.icon} ${toolName}`, style); -} - -export function bool( - value: boolean, - version?: typeof DEFAULT_VERSION | typeof BUSY_IDLE | typeof INVERSE_COLOR, -) { - return applyBooleanStyle(value, version); -} - -export function num(value: number, inverse = false) { - return applyNumberStyle(value, inverse); -} - -export function label(value: string) { - return applyStyle(value, UIConfig.labels.default); -} - -export function agentPoolId(value: AgentPoolId) { - return applyAgentPoolIdStyle(value); -} -export function agentPoolTypeId(value: AgentPoolTypeId) { - return applyAgentIdStyle(value); -} -export function agentId(value: AgentId | AgentPoolTypeId) { - return applyAgentIdStyle(value); -} -export function agentKind(value: string) { - return applyStyle(value, UIConfig.labels.agentKind); -} -export function agentType(value: string) { - return applyStyle(value, UIConfig.labels.agentType); -} -export function taskId(value: string) { - return applyStyle(value, UIConfig.labels.taskId); -} -export function desc(description: string) { - return applyStyle(description, UIConfig.labels.description); -} -export function tools(tools: AvailableTool[]) { - return applyToolsStyle(tools); -} - -export function timestamp(timestamp: string) { - return applyStyle(timestamp, UIConfig.labels.timestamp); -} - -export function eventType(event: string) { - return applyStyle(event, UIConfig.labels.eventType); -} -export function error(error: string) { - return applyStyle(error, UIConfig.labels.error); -} - -export function input(input: string) { - return applyStyle(input, UIConfig.labels.input); -} -export function agentPool(agentPool: AgentPool): string { - return `${agentId(agentPool.agentConfig)} [${num(agentPool.poolStats.available)}/${num(agentPool.poolStats.poolSize)}]`; -} -export function agent(agent: Agent) { - return `${agentId(agent.agentId)} ${bool(agent.inUse, agent.inUse ? DEFAULT_VERSION : BUSY_IDLE)}`; -} From 41c712d322da392f8e18a3fc0d3c2fb5a4dc96ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Tue, 18 Feb 2025 02:26:07 +0100 Subject: [PATCH 05/13] chore: titles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- src/ui/supervisor-monitor.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/supervisor-monitor.ts b/src/ui/supervisor-monitor.ts index 3c39825..3aa5cce 100644 --- a/src/ui/supervisor-monitor.ts +++ b/src/ui/supervisor-monitor.ts @@ -19,8 +19,8 @@ new AgentMonitor({ mouse: true, keys: true, vi: true, - border: { type: "line" }, - label: " Agent Monitor ", + border: { type: "bg" }, + label: "■■■ AGENT MONITOR ■■■", }), }).start(); @@ -35,7 +35,7 @@ new TaskMonitor({ mouse: true, keys: true, vi: true, - border: { type: "line" }, - label: " Task Monitor ", + border: { type: "bg" }, + label: "■■■ TASK MONITOR ■■■", }), }).start(); From 079c257115054a5012fa7df3dd77044060ac1494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Wed, 19 Feb 2025 00:43:18 +0100 Subject: [PATCH 06/13] feat: add workspace persistence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- .../resources-access-control.ts | 5 + src/agents/registry/registry.ts | 87 ++++-- src/main.ts | 32 ++- src/tasks/manager/dto.ts | 6 + src/tasks/manager/manager.ts | 69 ++++- src/utils/file.ts | 17 ++ src/workspace/workspace-manager.ts | 267 ++++++++++++++++++ src/workspace/workspace-restorable.ts | 46 +++ .../default/configs/agent_registry.jsonl | 2 + workspaces/default/configs/task_manager.jsonl | 20 ++ 10 files changed, 522 insertions(+), 29 deletions(-) create mode 100644 src/utils/file.ts create mode 100644 src/workspace/workspace-manager.ts create mode 100644 src/workspace/workspace-restorable.ts create mode 100644 workspaces/default/configs/agent_registry.jsonl create mode 100644 workspaces/default/configs/task_manager.jsonl diff --git a/src/access-control/resources-access-control.ts b/src/access-control/resources-access-control.ts index efef064..f7dcbe0 100644 --- a/src/access-control/resources-access-control.ts +++ b/src/access-control/resources-access-control.ts @@ -59,6 +59,11 @@ export class ResourcesAccessControl { }); } + public getResourcePermissionsByAdmin(resourceId: ResourceId, actingUserId: UserId) { + this.checkPermission(REGISTRY_RESOURCE, actingUserId, ["read"]); + return clone(this.getResourcePermissions(resourceId)); + } + private getResourcePermissions(resourceId: ResourceId, throwError = true) { const resourcePermissions = this.registry.resources.get(resourceId); if (!resourcePermissions && throwError) { diff --git a/src/agents/registry/registry.ts b/src/agents/registry/registry.ts index f05eed7..b0d6b58 100644 --- a/src/agents/registry/registry.ts +++ b/src/agents/registry/registry.ts @@ -1,7 +1,8 @@ -import { Logger } from "bee-agent-framework/logger/logger"; import { clone, isNonNullish } from "remeda"; import { BaseToolsFactory } from "src/base/tools-factory.js"; import { updateDeepPartialObject } from "src/utils/objects.js"; +import { WorkspaceResource } from "src/workspace/workspace-manager.js"; +import { WorkspaceRestorable } from "src/workspace/workspace-restorable.js"; import { agentConfigIdToValue, agentIdToString, @@ -15,6 +16,7 @@ import { AgentConfig, AgentConfigIdValue, AgentConfigPoolStats, + AgentConfigSchema, AgentConfigVersionValue, AgentIdValue, AgentKindEnum, @@ -72,20 +74,10 @@ export interface AgentInstanceRef { export type AgentTypesMap = Map>; -/** - * Registry for managing agent types, instances, and pools. - * Provides functionality for: - * - Registering and managing agent types - * - Creating and destroying agent instances - * - Managing pools of reusable agents - * - Tracking agent lifecycle and utilization - * - * Usage: - * - Whenever you need an agent first look if there already is an suitable agent type if not let register new one. - * - */ -export class AgentRegistry { - private readonly logger: Logger; +const AGENT_REGISTRY_USER = "agent_registry_user"; +const AGENT_REGISTRY_CONFIG_PATH = ["configs", "agent_registry.jsonl"] as const; + +export class AgentRegistry extends WorkspaceRestorable { /** Map of registered agent kind and their configurations */ private agentConfigs: Map>; /** Map of all agent instances */ @@ -115,7 +107,7 @@ export class AgentRegistry { onAgentConfigCreated: (agentKind: AgentKindEnum, agentType: string) => void; agentLifecycle: AgentLifecycleCallbacks; }) { - this.logger = Logger.root.child({ name: "AgentRegistry" }); + super(AGENT_REGISTRY_CONFIG_PATH, AGENT_REGISTRY_USER); this.logger.info("Initializing AgentRegistry"); this.lifecycleCallbacks = agentLifecycle; this.onAgentConfigCreated = onAgentConfigCreated; @@ -124,6 +116,48 @@ export class AgentRegistry { this.agentPools = new Map(AgentKindEnumSchema.options.map((kind) => [kind, new Map()])); } + restore(): void { + super.restore(AGENT_REGISTRY_USER); + } + + protected restoreEntity(resource: WorkspaceResource, line: string): void { + this.logger.info(`Restoring previous state from ${resource.path}`); + + let parsed; + try { + parsed = JSON.parse(line); + } catch (e) { + this.logger.error(e); + throw new Error(`Failed to parse JSON: ${line}`); + } + + const agentConfigResult = AgentConfigSchema.safeParse(parsed); + if (agentConfigResult.success) { + this.createAgentConfig(agentConfigResult.data, false); + return; + } + + this.logger.error(agentConfigResult, `Can't restore agent config`); + throw new Error(`Can't restore`); + } + + protected getSerializedEntities(): string { + return Array.from(this.agentConfigs.entries()) + .map(([agentKind, typeMap]) => + Array.from(typeMap.entries()).map(([agentType, versions]) => { + const agentConfig = versions.at(-1); + if (!agentConfig) { + throw new Error( + `Agent config ${agentSomeIdToTypeValue({ agentKind, agentType })} has no version to serialize`, + ); + } + return JSON.stringify(agentConfig); + }), + ) + .flat() + .join("\n"); + } + /** * Register tools factory for a specific agent type * @param tuples @@ -204,6 +238,7 @@ export class AgentRegistry { createAgentConfig( config: Omit, + persist = true, ): AgentConfig { const { agentKind, agentType, maxPoolSize } = config; this.logger.info("Create new agent config", { @@ -253,6 +288,10 @@ export class AgentRegistry { this.initializeAgentPool(agentKind, agentType, agentConfigVersion); this.onAgentConfigCreated(agentKind, agentType); + if (persist) { + this.persist(); + } + return configVersioned; } @@ -487,6 +526,22 @@ export class AgentRegistry { .flat(); } + isAgentConfigExists( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + agentConfigVersion?: number, + ) { + let exists = false; + try { + this.getAgentConfig(agentKind, agentType, agentConfigVersion); + exists = true; + } catch (err) { + this.logger.warn(err, `Agent config doesn't exist`); + } + + return exists; + } + getAgentConfig( agentKind: AgentKindEnum, agentType: AgentTypeValue, diff --git a/src/main.ts b/src/main.ts index 437246e..a1bbbed 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,11 +11,17 @@ import * as supervisor from "./agents/supervisor.js"; import { createConsoleReader } from "./helpers/reader.js"; import { TaskManager } from "./tasks/manager/manager.js"; import { taskStateLogger } from "./tasks/state/logger.js"; +import { WorkspaceManager } from "./workspace/workspace-manager.js"; // Reset audit logs agentStateLogger(); taskStateLogger(); +const workspaceManager = WorkspaceManager.getInstance(); +// Setup workspace +workspaceManager.setWorkspaceDirPath(["workspaces"]); +workspaceManager.setWorkspace("default"); + const registry = new AgentRegistry({ agentLifecycle: { async onCreate( @@ -91,15 +97,21 @@ registry.registerToolsFactories([ ["operator", new operator.ToolsFactory()], ]); -registry.createAgentConfig({ - autoPopulatePool: false, - agentKind: AgentKindEnumSchema.Enum.supervisor, - agentType: supervisor.AgentTypes.BOSS, - instructions: "", - tools: registry.getToolsFactory(AgentKindEnumSchema.Enum.supervisor).getAvailableToolsNames(), - description: "The boss supervisor agent that control whole app.", - maxPoolSize: 1, -}); +registry.restore(); + +if ( + !registry.isAgentConfigExists(AgentKindEnumSchema.Enum.supervisor, supervisor.AgentTypes.BOSS) +) { + registry.createAgentConfig({ + autoPopulatePool: false, + agentKind: AgentKindEnumSchema.Enum.supervisor, + agentType: supervisor.AgentTypes.BOSS, + instructions: "", + tools: registry.getToolsFactory(AgentKindEnumSchema.Enum.supervisor).getAvailableToolsNames(), + description: "The boss supervisor agent that control whole app.", + maxPoolSize: 1, + }); +} const { instance: supervisorAgent, agentId: supervisorAgentId } = await registry.acquireAgent( AgentKindEnumSchema.Enum.supervisor, @@ -107,6 +119,7 @@ const { instance: supervisorAgent, agentId: supervisorAgentId } = await registry ); taskManager.registerAdminAgent(supervisorAgentId); +taskManager.restore(supervisorAgentId); // Can you create tasks to write poem about: sun, earth, mars and assign them to the right agent type and run them? // Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types run all tasks and give me the created poems when it will be all finished? @@ -122,6 +135,7 @@ taskManager.registerAdminAgent(supervisorAgentId); // Can you create different kinds of specialized agents that will do a research on different aspects of person profile from internet? You should be very specific and explanatory in their instructions. Don't create any tasks. // Base on these agents can you prepare related tasks. And one extra agent and task that will summarize task outputs other tasks. +// Can you create a personal profile of Dario Gil? const reader = createConsoleReader({ fallback: "What is the current weather in Las Vegas?" }); for await (const { prompt } of reader) { diff --git a/src/tasks/manager/dto.ts b/src/tasks/manager/dto.ts index 720678c..6d5cc9d 100644 --- a/src/tasks/manager/dto.ts +++ b/src/tasks/manager/dto.ts @@ -84,6 +84,12 @@ export const TaskConfigSchema = z export type TaskConfig = z.infer; +export const TaskConfigOwnedResourceSchema = z.object({ + taskConfig: TaskConfigSchema, + ownerId: z.string(), +}); +export type TaskConfigOwnedResource = z.infer; + export const TaskRunTerminalStatusEnumSchema = z.enum(["STOPPED", "FAILED", "COMPLETED"]); export type TaskRunTerminalStatusEnum = z.infer; diff --git a/src/tasks/manager/manager.ts b/src/tasks/manager/manager.ts index 2c7771b..9398350 100644 --- a/src/tasks/manager/manager.ts +++ b/src/tasks/manager/manager.ts @@ -1,5 +1,4 @@ import { FrameworkError } from "bee-agent-framework"; -import { Logger } from "bee-agent-framework/logger/logger"; import { clone, isNonNullish, omit } from "remeda"; import { FULL_ACCESS, @@ -14,6 +13,7 @@ import { AgentIdValue, AgentKindEnum, AgentTypeValue } from "src/agents/registry import { agentStateLogger } from "src/agents/state/logger.js"; import { taskStateLogger } from "src/tasks/state/logger.js"; import { updateDeepPartialObject } from "src/utils/objects.js"; +import { WorkspaceResource } from "src/workspace/workspace-manager.js"; import { stringToTaskRun, taskConfigIdToValue, @@ -25,6 +25,8 @@ import { isTaskRunTerminationStatus, TaskConfig, TaskConfigIdValue, + TaskConfigOwnedResource, + TaskConfigOwnedResourceSchema, TaskConfigPoolStats, TaskConfigVersionValue, TaskKindEnum, @@ -35,6 +37,7 @@ import { TaskRunTerminalStatusEnum, TaskTypeValue, } from "./dto.js"; +import { WorkspaceRestorable } from "src/workspace/workspace-restorable.js"; export type TaskRunRuntime = TaskRun & { intervalId: NodeJS.Timeout | null; @@ -42,11 +45,11 @@ export type TaskRunRuntime = TaskRun & { const TASK_MANAGER_RESOURCE = "task_manager"; const TASK_MANAGER_USER = "task_manager_user"; +const TASK_MANAGER_CONFIG_PATH = ["configs", "task_manager.jsonl"] as const; const MAX_POOL_SIZE = 100; -export class TaskManager { - private readonly logger: Logger; +export class TaskManager extends WorkspaceRestorable { /** Map of registered task type and their configurations */ private taskConfigs: Map>; private taskRuns = new Map(); @@ -91,7 +94,7 @@ export class TaskManager { maxHistoryEntries?: number; } = {}, ) { - this.logger = Logger.root.child({ name: "TaskManager" }); + super(TASK_MANAGER_CONFIG_PATH, TASK_MANAGER_USER); this.logger.info("Initializing TaskManager"); this.ac = new ResourcesAccessControl(this.constructor.name, [TASK_MANAGER_USER]); @@ -121,6 +124,56 @@ export class TaskManager { }, 100); // Runs every 100ms (0.1 second) } + protected restoreEntity( + resource: WorkspaceResource, + line: string, + actingAgentId: AgentIdValue, + ): void { + this.logger.info(`Restoring previous state from ${resource.path}`); + let parsed; + try { + parsed = JSON.parse(line); + } catch (e) { + this.logger.error(e); + throw new Error(`Failed to parse JSON: ${line}`); + } + const taskConfigResult = TaskConfigOwnedResourceSchema.safeParse(parsed); + if (taskConfigResult.success) { + this.createTaskConfig( + taskConfigResult.data.taskConfig, + taskConfigResult.data.ownerId, + actingAgentId, + false, + ); + return; + } + + this.logger.error(taskConfigResult, `Can't restore task config`); + throw new Error(`Can't restore`); + } + + protected getSerializedEntities(): string { + return Array.from(this.taskConfigs.entries()) + .map(([taskKind, typeMap]) => + Array.from(typeMap.entries()).map(([taskType, versions]) => { + const taskConfig = versions.at(-1); + if (!taskConfig) { + throw new Error( + `Task ${taskSomeIdToTypeValue({ taskKind, taskType })} has no version to serialize`, + ); + } + const { ownerId } = this.ac.getResourcePermissionsByAdmin( + taskConfig.taskConfigId, + TASK_MANAGER_USER, + )!; + const config = { ownerId, taskConfig } satisfies TaskConfigOwnedResource; + return JSON.stringify(config); + }), + ) + .flat() + .join("\n"); + } + registerAdminAgent(agentId: AgentIdValue) { this.ac.createPermissions(TASK_MANAGER_RESOURCE, agentId, FULL_ACCESS, TASK_MANAGER_USER); } @@ -318,6 +371,7 @@ export class TaskManager { config: Omit, ownerAgentId: string, actingAgentId: AgentIdValue, + persist = true, ): TaskConfig { const { taskKind, taskType, maxRepeats: maxRuns } = config; this.logger.info("Create new task config", { @@ -364,6 +418,10 @@ export class TaskManager { this.initializeTaskPool(taskKind, taskType, taskConfigVersion); + if (persist) { + this.persist(); + } + return configVersioned; } @@ -490,6 +548,7 @@ export class TaskManager { config: newConfigVersion, }); + this.persist(); return newConfigVersion; } @@ -553,6 +612,8 @@ export class TaskManager { if (!this.getTaskConfigMap(taskKind).size) { this.taskConfigs.delete(taskKind); } + + this.persist(); } createTaskRun( diff --git a/src/utils/file.ts b/src/utils/file.ts new file mode 100644 index 0000000..4d6e65a --- /dev/null +++ b/src/utils/file.ts @@ -0,0 +1,17 @@ +import { join, normalize, relative } from "path"; + +export function validatePath(parentDir: string, targetPath: string): string { + // Normalize paths to remove any '..' or '.' segments and resolve relative paths + const normalizedParent = normalize(parentDir); + const normalizedTarget = normalize(join(normalizedParent, targetPath)); + + // Get relative path to check if it stays within parent + const relativePath = relative(normalizedParent, normalizedTarget); + + if (relativePath.startsWith("..") || targetPath.startsWith("/")) { + throw new Error(`Path traversal attempt detected for path: "${targetPath}"`); + } + + // Return the clean relative path + return relativePath; +} diff --git a/src/workspace/workspace-manager.ts b/src/workspace/workspace-manager.ts new file mode 100644 index 0000000..038e7ad --- /dev/null +++ b/src/workspace/workspace-manager.ts @@ -0,0 +1,267 @@ +import { Logger } from "bee-agent-framework"; +import EventEmitter from "events"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import path, { basename } from "path"; +import { clone } from "remeda"; +import { validatePath } from "src/utils/file.js"; + +export const WORKSPACES_DIR_PATH = ["..", "..", "workspaces"] as const; +export const DEFAULT_WORKSPACE_DIR_NAME = "default"; + +interface WorkspaceManagerEvents { + "workspace:reset": () => void; + "workspace:change": (resource: WorkspaceResource) => void; +} + +export interface WorkspaceResource { + isDirectory: boolean; + name: string; + path: string; + ownerId: string; +} + +type DirPath = string; + +export interface CreateResourceInput { + kind: "file" | "directory"; + path: readonly string[]; +} + +export interface CreateDirectoryResourceInput extends CreateResourceInput { + kind: "directory"; +} + +export interface CreateFileResourceInput extends CreateResourceInput { + kind: "file"; +} + +export class WorkspaceManager extends EventEmitter { + private logger: Logger; + private static instance: WorkspaceManager; + private _workspacesDirPath?: string; + private _workspaceName?: string; + private _workspacePath?: string; + private resources = new Map(); + + get workspaceName() { + if (!this._workspaceName) { + throw Error(`Workspace wasn't set yet`); + } + + return this._workspaceName; + } + + get workspacesDirPath() { + if (!this._workspacesDirPath) { + throw Error(`Workspace dir path wasn't set yet`); + } + + return this._workspacesDirPath; + } + + get workspacePath() { + if (!this._workspacePath) { + throw Error(`Workspace path wasn't set yet`); + } + + return this._workspacePath; + } + + setWorkspaceDirPath(dirPath: string[]) { + const joinedPath = path.join(...dirPath); + const dir = path.dirname(joinedPath); + if (!existsSync(dir)) { + throw new Error(`Workspace directory ${joinedPath} doesn't exists`); + } + this._workspacesDirPath = joinedPath; + } + + static getInstance() { + let instance = WorkspaceManager.instance; + if (!instance) { + instance = WorkspaceManager.instance = new WorkspaceManager(); + } + + return instance; + } + + private constructor() { + super(); + this.logger = Logger.root.child({ name: "WorkspaceManager" }); + } + + private ensureDirectoryExists(dirPath: string) { + const validDirPath = validatePath(this.workspacesDirPath, dirPath); + + try { + if (!existsSync(validDirPath)) { + mkdirSync(validDirPath, { recursive: true }); + this.logger.info(`Created directory: ${dirPath}`); + } + } catch (error) { + this.logger.error(`Error creating directory: ${dirPath}`); + throw error; + } + } + + public on( + event: K, + listener: WorkspaceManagerEvents[K], + ): this { + return super.on(event, listener); + } + + public emit( + event: K, + ...args: Parameters + ): boolean { + return super.emit(event, ...args); + } + + setWorkspace(workspaceName: string) { + const workspaceNameSanitized = basename(workspaceName); + const workspacePathSanitized = path.join(this.workspacesDirPath, workspaceNameSanitized); + this.ensureDirectoryExists(workspacePathSanitized); + this._workspaceName = workspaceNameSanitized; + this._workspacePath = workspacePathSanitized; + } + + registerResource( + input: CreateFileResourceInput | CreateDirectoryResourceInput, + ownerId: string, + ): WorkspaceResource { + const inputJoinedPath = path.join(this.workspacePath, ...input.path); + const validPath = validatePath(this.workspacePath, inputJoinedPath); + const name = basename(validPath); + + // Check if resource already exists in our tracking + const existingResource = this.resources.get(inputJoinedPath); + if (existingResource) { + throw new Error( + `Resource on path ${inputJoinedPath} already exists and is owned by ${existingResource.ownerId}`, + ); + } + + try { + if (input.kind === "directory") { + if (!existsSync(validPath)) { + // Create directory with all subdirectories only if it doesn't exist + mkdirSync(validPath, { recursive: true }); + this.logger.info(`Created directory: ${input.path}`); + } else { + this.logger.info(`Directory already exists, skipping creation: ${input.path}`); + } + } else { + if (!existsSync(validPath)) { + // For files, first ensure parent directory exists + const parentDir = path.dirname(validPath); + if (!existsSync(parentDir)) { + mkdirSync(parentDir, { recursive: true }); + } + + // Create empty file only if it doesn't exist + writeFileSync(validPath, ""); + this.logger.info(`Created file: ${input.path}`); + } else { + this.logger.info(`File already exists, skipping creation: ${input.path}`); + } + } + + const resource = { + name, + path: validPath, + ownerId, + isDirectory: input.kind === "directory", + }; + // Track the new resource + this.resources.set(inputJoinedPath, resource); + return clone(resource); + } catch (error) { + this.logger.error(`Error creating resource: ${inputJoinedPath} owned by ${ownerId}`, error); + throw error; + } + } + + /** + * Reads the content of a file resource + * @param path Path to the resource + * @param ownerId ID of the resource owner + * @param onLine Callback to handle the resource content + * @throws Error if resource doesn't exist, belongs to different owner, is a directory, or can't be read + */ + readResource( + resourcePath: string, + ownerId: string, + onLine: (resource: WorkspaceResource, content: string) => void, + ): void { + // Check if resource exists in our tracking + const resource = this.resources.get(resourcePath); + if (!resource) { + throw new Error(`Resource not found: ${resourcePath}`); + } + + // Verify ownership + if (resource.ownerId !== ownerId) { + throw new Error(`Access denied: Resource ${resourcePath} is owned by ${resource.ownerId}`); + } + + // Check if it's a directory + if (resource.isDirectory) { + throw new Error(`Cannot read content of directory: ${resourcePath}`); + } + + try { + // Read file content synchronously + const content = readFileSync(resource.path, { encoding: "utf8" }); + + // Split content into lines and process each line + const lines = content.split(/\r?\n/); + for (const line of lines) { + onLine(resource, line); + } + + this.logger.info(`Successfully read file: ${resourcePath}`); + } catch (error) { + this.logger.error(`Error setting up file read: ${resourcePath}`, error); + throw new Error(`Failed to read file ${resourcePath}: ${error.message}`); + } + } + + /** + * Writes content to a file resource + * @param resourcePath Path to the resource + * @param resourceOwnerId ID of the resource owner + * @param content Content to write to the file + * @throws Error if resource doesn't exist, belongs to different owner, is a directory, or can't be written + */ + writeResource(resourcePath: string, resourceOwnerId: string, content: string): void { + // Check if resource exists in our tracking + const resource = this.resources.get(resourcePath); + if (!resource) { + throw new Error(`Resource not found: ${resourcePath}`); + } + + // Verify ownership + if (resource.ownerId !== resourceOwnerId) { + throw new Error(`Access denied: Resource ${resourcePath} is owned by ${resource.ownerId}`); + } + + // Check if it's a directory + if (resource.isDirectory) { + throw new Error(`Cannot write content to directory: ${resourcePath}`); + } + + try { + // Write content to file + writeFileSync(resource.path, content, "utf8"); + + this.logger.info(`Successfully wrote to file: ${resourcePath}`); + + // Emit change event with the updated resource + this.emit("workspace:change", clone(resource)); + } catch (error) { + this.logger.error(`Error writing to file: ${resourcePath}`, error); + throw new Error(`Failed to write to file ${resourcePath}: ${error.message}`); + } + } +} diff --git a/src/workspace/workspace-restorable.ts b/src/workspace/workspace-restorable.ts new file mode 100644 index 0000000..5828e91 --- /dev/null +++ b/src/workspace/workspace-restorable.ts @@ -0,0 +1,46 @@ +import { Logger } from "bee-agent-framework"; +import { AgentIdValue } from "src/agents/registry/dto.js"; +import { WorkspaceManager, WorkspaceResource } from "src/workspace/workspace-manager.js"; + +export abstract class WorkspaceRestorable { + protected readonly logger: Logger; + protected workspaceManager: WorkspaceManager; + protected resource: WorkspaceResource; + protected resourceOwnerId: string; + + constructor(path: readonly string[], resourceOwnerId: string) { + this.logger = Logger.root.child({ name: this.constructor.name }); + this.resourceOwnerId = resourceOwnerId; + this.workspaceManager = WorkspaceManager.getInstance(); + this.resource = this.workspaceManager.registerResource( + { + kind: "file", + path, + }, + resourceOwnerId, + ); + } + + persist(): void { + const entities = this.getSerializedEntities(); + this.workspaceManager.writeResource(this.resource.path, this.resourceOwnerId, entities); + } + + protected abstract getSerializedEntities(): string; + + restore(actingAgentId: AgentIdValue): void { + this.workspaceManager.readResource( + this.resource.path, + this.resourceOwnerId, + (resource, content) => { + this.restoreEntity(resource, content, actingAgentId); + }, + ); + } + + protected abstract restoreEntity( + resource: WorkspaceResource, + line: string, + actingAgentId: AgentIdValue, + ): void; +} diff --git a/workspaces/default/configs/agent_registry.jsonl b/workspaces/default/configs/agent_registry.jsonl new file mode 100644 index 0000000..95e5d47 --- /dev/null +++ b/workspaces/default/configs/agent_registry.jsonl @@ -0,0 +1,2 @@ +{"agentConfigId":"supervisor:boss:1","agentConfigVersion":1,"agentKind":"supervisor","agentType":"boss","instructions":"","description":"The boss supervisor agent that control whole app.","tools":["agent_registry","task_runner"],"maxPoolSize":1,"autoPopulatePool":false} +{"agentConfigId":"operator:poem_generator:1","agentConfigVersion":1,"agentType":"poem_generator","instructions":"Generate a poem based on the input topic.","description":"An agent that generates poems on a given topic.","tools":[],"maxPoolSize":5,"autoPopulatePool":true,"agentKind":"operator"} \ No newline at end of file diff --git a/workspaces/default/configs/task_manager.jsonl b/workspaces/default/configs/task_manager.jsonl new file mode 100644 index 0000000..3ad8d82 --- /dev/null +++ b/workspaces/default/configs/task_manager.jsonl @@ -0,0 +1,20 @@ +{ + "ownerId": "supervisor:boss[1]:1", + "taskConfig": { + "taskKind": "operator", + "taskType": "poem_generation", + "taskConfigInput": "{}", + "description": "Generate a poem on a given topic.", + "intervalMs": 0, + "runImmediately": false, + "agentKind": "operator", + "agentType": "poem_generator", + "agentNum": 1, + "agentVersion": 1, + "concurrencyMode": "PARALLEL", + "maxRepeats": null, + "taskConfigId": "operator:poem_generation:1", + "ownerAgentId": "supervisor:boss[1]:1", + "taskConfigVersion": 1 + } +} From e509d3e8f05a9ab8b8d043b3292e65241c9d5340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Wed, 19 Feb 2025 10:45:31 +0100 Subject: [PATCH 07/13] fixup! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- src/main.ts | 7 +++++- src/tasks/manager/manager.ts | 25 ++++++++----------- src/ui/task-monitor/monitor.ts | 5 ++-- src/workspace/workspace-manager.ts | 4 +++ workspaces/default/configs/task_manager.jsonl | 21 +--------------- 5 files changed, 24 insertions(+), 38 deletions(-) diff --git a/src/main.ts b/src/main.ts index a1bbbed..62aac96 100644 --- a/src/main.ts +++ b/src/main.ts @@ -88,7 +88,12 @@ const taskManager = new TaskManager( .then((resp) => onAgentComplete(resp.result.text, taskRun.taskRunId, agent.agentId, taskManager), ) - .catch((err) => onAgentError(err, taskRun.taskRunId, agent.agentId, taskManager)); + .catch((err) => { + onAgentError(err, taskRun.taskRunId, agent.agentId, taskManager); + }) + .finally(() => { + registry.releaseAgent(agent.agentId); + }); }, ); diff --git a/src/tasks/manager/manager.ts b/src/tasks/manager/manager.ts index 9398350..ed95e3f 100644 --- a/src/tasks/manager/manager.ts +++ b/src/tasks/manager/manager.ts @@ -14,12 +14,8 @@ import { agentStateLogger } from "src/agents/state/logger.js"; import { taskStateLogger } from "src/tasks/state/logger.js"; import { updateDeepPartialObject } from "src/utils/objects.js"; import { WorkspaceResource } from "src/workspace/workspace-manager.js"; -import { - stringToTaskRun, - taskConfigIdToValue, - taskRunIdToString, - taskSomeIdToTypeValue, -} from "../task-id.js"; +import { WorkspaceRestorable } from "src/workspace/workspace-restorable.js"; +import { taskConfigIdToValue, taskRunIdToString, taskSomeIdToTypeValue } from "../task-id.js"; import { isTaskRunActiveStatus, isTaskRunTerminationStatus, @@ -37,7 +33,6 @@ import { TaskRunTerminalStatusEnum, TaskTypeValue, } from "./dto.js"; -import { WorkspaceRestorable } from "src/workspace/workspace-restorable.js"; export type TaskRunRuntime = TaskRun & { intervalId: NodeJS.Timeout | null; @@ -794,11 +789,7 @@ export class TaskManager extends WorkspaceRestorable { stopTaskRun(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue, isCompleted = false): void { this.logger.info("Stopping task", { taskRunId, actingAgentId }); - this.ac.checkPermission( - taskSomeIdToTypeValue(stringToTaskRun(taskRunId)), - actingAgentId, - READ_WRITE_ACCESS, - ); + this.ac.checkPermission(taskRunId, actingAgentId, READ_WRITE_ACCESS); const taskRun = this.getTaskRun(taskRunId, actingAgentId); if (taskRun.status === "STOPPED") { @@ -1099,15 +1090,19 @@ export class TaskManager extends WorkspaceRestorable { maxRuns: taskRun.config.maxRepeats, }); - taskManager.releaseTaskRunOccupancy(taskRunId, agentId); // Check if we've reached maxRuns - if (taskRun.config.maxRepeats && taskRun.completedRuns >= taskRun.config.maxRepeats) { - taskManager.stopTaskRun(taskRunId, taskRun.config.ownerAgentId); + if ( + !taskRun.config.maxRepeats || + (taskRun.config.maxRepeats && taskRun.completedRuns >= taskRun.config.maxRepeats) + ) { + taskManager.stopTaskRun(taskRunId, agentId); taskManager.logger.info("Task reached maximum runs and has been stopped", { taskRunId, completedRuns: taskRun.completedRuns, maxRuns: taskRun.config.maxRepeats, }); + } else { + taskManager.releaseTaskRunOccupancy(taskRunId, agentId); } }, async onAgentError(err, taskRunId, agentId, taskManager) { diff --git a/src/ui/task-monitor/monitor.ts b/src/ui/task-monitor/monitor.ts index 1001ffc..f0ebf49 100644 --- a/src/ui/task-monitor/monitor.ts +++ b/src/ui/task-monitor/monitor.ts @@ -85,8 +85,7 @@ export class TaskMonitor extends BaseMonitor { }); this.stateBuilder.on("error", (error: Error) => { - // OK - console.error("Error occurred:", error); + this.logBox.log(`Error occurred: ${JSON.stringify(error)}`); }); // Left column - Pools and Task Runs (70%) @@ -459,6 +458,8 @@ export class TaskMonitor extends BaseMonitor { `${st.label("Version")}: ${st.versionNum(taskConfig.taskConfigVersion)}`, `${st.label("Task Kind")}: ${st.taskKind(taskConfig.taskKind)}`, `${st.label("Task Type")}: ${st.taskType(taskConfig.taskType)}`, + `${st.label("Max Repeats")}: ${st.num(taskConfig.maxRepeats || 0)}`, + `${st.label("Interval ms")}: ${st.num(taskConfig.intervalMs)}`, `${st.label("Concurrency Mode")}: ${st.concurrencyMode(taskConfig.concurrencyMode)}`, "", `${st.label("Description")}: `, diff --git a/src/workspace/workspace-manager.ts b/src/workspace/workspace-manager.ts index 038e7ad..fdb3d27 100644 --- a/src/workspace/workspace-manager.ts +++ b/src/workspace/workspace-manager.ts @@ -213,6 +213,10 @@ export class WorkspaceManager extends EventEmitter { try { // Read file content synchronously const content = readFileSync(resource.path, { encoding: "utf8" }); + if (content.length === 0) { + // Nothing to parse + return; + } // Split content into lines and process each line const lines = content.split(/\r?\n/); diff --git a/workspaces/default/configs/task_manager.jsonl b/workspaces/default/configs/task_manager.jsonl index 3ad8d82..1985d56 100644 --- a/workspaces/default/configs/task_manager.jsonl +++ b/workspaces/default/configs/task_manager.jsonl @@ -1,20 +1 @@ -{ - "ownerId": "supervisor:boss[1]:1", - "taskConfig": { - "taskKind": "operator", - "taskType": "poem_generation", - "taskConfigInput": "{}", - "description": "Generate a poem on a given topic.", - "intervalMs": 0, - "runImmediately": false, - "agentKind": "operator", - "agentType": "poem_generator", - "agentNum": 1, - "agentVersion": 1, - "concurrencyMode": "PARALLEL", - "maxRepeats": null, - "taskConfigId": "operator:poem_generation:1", - "ownerAgentId": "supervisor:boss[1]:1", - "taskConfigVersion": 1 - } -} +{"ownerId":"supervisor:boss[1]:1","taskConfig":{"taskKind":"operator","taskType":"poem_generation","taskConfigInput":"{}","description":"Generate a poem based on the input topic.","intervalMs":0,"runImmediately":false,"agentKind":"operator","agentType":"poem_generator","agentNum":1,"agentVersion":1,"concurrencyMode":"PARALLEL","maxRetries":3,"retryDelayMs":1000,"taskConfigId":"operator:poem_generation:1","ownerAgentId":"supervisor:boss[1]:1","taskConfigVersion":1}} \ No newline at end of file From 3aa51defbb1ddc35e6733a4dc15a56122ad7c4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Wed, 19 Feb 2025 10:52:28 +0100 Subject: [PATCH 08/13] fixup! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- src/ui/agent-monitor/monitor.ts | 12 ++++++------ src/ui/task-monitor/monitor.ts | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ui/agent-monitor/monitor.ts b/src/ui/agent-monitor/monitor.ts index 1a727c4..ebca84d 100644 --- a/src/ui/agent-monitor/monitor.ts +++ b/src/ui/agent-monitor/monitor.ts @@ -128,7 +128,7 @@ export class AgentMonitor extends BaseMonitor { this.agentList = blessed.list({ parent: this.parent, width: "30%", - height: "30%", + height: "50%", left: 0, top: "40%", border: { type: "line" }, @@ -164,7 +164,7 @@ export class AgentMonitor extends BaseMonitor { this.agentDetail = blessed.box({ parent: this.parent, width: "40%", - height: "30%", + height: "50%", left: "30%", top: "40%", border: { type: "line" }, @@ -182,8 +182,8 @@ export class AgentMonitor extends BaseMonitor { this.lifecycleHistory = blessed.box({ parent: this.parent, width: "30%", - height: "70%", - right: 0, + height: "90%", + left: "70%", top: 0, border: { type: "line" }, label: " Lifecycle Events ", @@ -200,9 +200,9 @@ export class AgentMonitor extends BaseMonitor { this.logBox = blessed.log({ parent: this.parent, width: "100%", - height: "30%", + height: "10%", left: 0, - bottom: 0, + top: "90%", border: { type: "line" }, label: " Live Updates ", tags: true, diff --git a/src/ui/task-monitor/monitor.ts b/src/ui/task-monitor/monitor.ts index f0ebf49..7bc4b26 100644 --- a/src/ui/task-monitor/monitor.ts +++ b/src/ui/task-monitor/monitor.ts @@ -109,7 +109,7 @@ export class TaskMonitor extends BaseMonitor { this.taskVersionsList = blessed.list({ parent: this.parent, width: "30%", - height: "20%", + height: "30%", left: 0, top: "20%", border: { type: "line" }, @@ -127,9 +127,9 @@ export class TaskMonitor extends BaseMonitor { this.taskRunList = blessed.list({ parent: this.parent, width: "30%", - height: "30%", + height: "40%", left: 0, - top: "40%", + top: "50%", border: { type: "line" }, label: " Task Runs ", content: TASK_RUN_LIST_DEFAULT_TEXT, @@ -146,7 +146,7 @@ export class TaskMonitor extends BaseMonitor { this.taskConfigDetail = blessed.box({ parent: this.parent, width: "40%", - height: "20%", + height: "30%", left: "30%", top: 0, border: { type: "line" }, @@ -163,9 +163,9 @@ export class TaskMonitor extends BaseMonitor { this.taskRunDetail = blessed.box({ parent: this.parent, width: "40%", - height: "50%", + height: "60%", left: "30%", - top: "20%", + top: "30%", border: { type: "line" }, label: " Task Run Detail ", content: TASK_RUN_DETAIL_DEFAULT_TEXT, @@ -181,9 +181,9 @@ export class TaskMonitor extends BaseMonitor { this.logBox = blessed.log({ parent: this.parent, width: "100%", - height: "30%", + height: "10%", left: 0, - bottom: 0, + top: "90%", border: { type: "line" }, label: " Live Updates ", tags: true, From 6dd2c05b7f496dcfee22b16d6a2fda9be156a093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Sat, 22 Feb 2025 14:42:10 +0100 Subject: [PATCH 09/13] chore: refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- package-lock.json | 2183 +++++- package.json | 16 +- pnpm-lock.yaml | 6552 +++++++++++++++++ src/agents/index.ts | 4 + src/agents/registry/registry.ts | 32 +- src/agents/state/logger.ts | 32 +- src/agents/supervisor.ts | 2 - src/agents/tool.ts | 190 - src/index.ts | 144 + src/main.ts | 21 +- src/tasks/index.ts | 2 + src/tasks/manager/manager.ts | 43 +- src/tasks/state/logger.ts | 33 +- src/ui/index.ts | 3 + src/ui/main.ts | 3 + src/ui/monitor.ts | 49 + src/ui/supervisor-monitor.ts | 41 - src/workspaces/index.ts | 1 + .../workspace-manager.ts | 26 +- .../workspace-restorable.ts | 2 +- start.sh | 94 - tsconfig.json | 12 +- 22 files changed, 8685 insertions(+), 800 deletions(-) create mode 100644 pnpm-lock.yaml create mode 100644 src/agents/index.ts create mode 100644 src/index.ts create mode 100644 src/tasks/index.ts create mode 100644 src/ui/index.ts create mode 100644 src/ui/main.ts create mode 100644 src/ui/monitor.ts delete mode 100644 src/ui/supervisor-monitor.ts create mode 100644 src/workspaces/index.ts rename src/{workspace => workspaces}/workspace-manager.ts (94%) rename src/{workspace => workspaces}/workspace-restorable.ts (93%) delete mode 100755 start.sh diff --git a/package-lock.json b/package-lock.json index 38e1f53..33416b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "bee-empty", + "name": "bee-supervisor", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "bee-empty", + "name": "bee-supervisor", "version": "0.0.1", "dependencies": { "@google-cloud/vertexai": "^1.9.2", @@ -57,9 +57,9 @@ } }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.7.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", - "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "version": "11.9.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.1.tgz", + "integrity": "sha512-OvyhwtYaWSTfo8NfibmFlgl+pIMaBOmN0OwZ3CPaGscEK3B8FCVDuQ7zgxY8seU/1kfSvNWnyB0DtKJyNLxX7g==", "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", @@ -72,373 +72,1074 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "optional": true, + "peer": true, "dependencies": { - "regenerator-runtime": "^0.14.0" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "optional": true, - "os": [ - "android" - ], + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "optional": true, - "os": [ - "android" - ], + "peer": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "optional": true, - "os": [ - "android" - ], + "peer": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "optional": true, - "os": [ - "darwin" - ], + "peer": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=16.0.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "optional": true, - "os": [ - "freebsd" - ], + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.751.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.751.0.tgz", + "integrity": "sha512-pKw4TDhO9oUnSvmTMLj2oG/SHZ1Q3tQb3DNkpdCbWqjBJ9pL0Dl+YZagLkHm0RZNp2jqAiHL7jiTCnfOAhPaBw==", "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/credential-provider-node": "3.750.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.750.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.4", + "@smithy/eventstream-serde-browser": "^4.0.1", + "@smithy/eventstream-serde-config-resolver": "^4.0.1", + "@smithy/eventstream-serde-node": "^4.0.1", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/middleware-retry": "^4.0.6", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.6", + "@smithy/util-defaults-mode-node": "^4.0.6", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-stream": "^4.1.1", + "@smithy/util-utf8": "^4.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", - "cpu": [ - "loong64" - ], - "dev": true, + "node_modules/@aws-sdk/client-bedrock-runtime/node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", "optional": true, - "os": [ - "linux" + "peer": true + }, + "node_modules/@aws-sdk/client-bedrock-runtime/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" ], - "engines": { - "node": ">=18" + "optional": true, + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", - "cpu": [ - "mips64el" - ], - "dev": true, + "node_modules/@aws-sdk/client-sso": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.750.0.tgz", + "integrity": "sha512-y0Rx6pTQXw0E61CaptpZF65qNggjqOgymq/RYZU5vWba5DGQ+iqGt8Yq8s+jfBoBBNXshxq8l8Dl5Uq/JTY1wg==", "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.750.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.4", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/middleware-retry": "^4.0.6", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.6", + "@smithy/util-defaults-mode-node": "^4.0.6", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@aws-sdk/core": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.750.0.tgz", + "integrity": "sha512-bZ5K7N5L4+Pa2epbVpUQqd1XLG2uU8BGs/Sd+2nbgTf+lNQJyIxAg/Qsrjz9MzmY8zzQIeRQEkNmR6yVAfCmmQ==", "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/core": "^3.1.4", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/signature-v4": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", - "cpu": [ - "riscv64" + "node_modules/@aws-sdk/core/node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } ], - "dev": true, "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.750.0.tgz", + "integrity": "sha512-In6bsG0p/P31HcH4DBRKBbcDS/3SHvEPjfXV8ODPWZO/l3/p7IRoYBdQ07C9R+VMZU2D0+/Sc/DWK/TUNDk1+Q==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-s390x": { + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.750.0.tgz", + "integrity": "sha512-wFB9qqfa20AB0dElsQz5ZlZT5o+a+XzpEpmg0erylmGYqEOvh8NQWfDUVpRmQuGq9VbvW/8cIbxPoNqEbPtuWQ==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/property-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.750.0.tgz", + "integrity": "sha512-2YIZmyEr5RUd3uxXpxOLD9G67Bibm4I/65M6vKFP17jVMUT+R1nL7mKqmhEVO2p+BoeV+bwMyJ/jpTYG368PCg==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/credential-provider-env": "3.750.0", + "@aws-sdk/credential-provider-http": "3.750.0", + "@aws-sdk/credential-provider-process": "3.750.0", + "@aws-sdk/credential-provider-sso": "3.750.0", + "@aws-sdk/credential-provider-web-identity": "3.750.0", + "@aws-sdk/nested-clients": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.750.0.tgz", + "integrity": "sha512-THWHHAceLwsOiowPEmKyhWVDlEUxH07GHSw5AQFDvNQtGKOQl0HSIFO1mKObT2Q2Vqzji9Bq8H58SO5BFtNPRw==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.750.0", + "@aws-sdk/credential-provider-http": "3.750.0", + "@aws-sdk/credential-provider-ini": "3.750.0", + "@aws-sdk/credential-provider-process": "3.750.0", + "@aws-sdk/credential-provider-sso": "3.750.0", + "@aws-sdk/credential-provider-web-identity": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.750.0.tgz", + "integrity": "sha512-Q78SCH1n0m7tpu36sJwfrUSxI8l611OyysjQeMiIOliVfZICEoHcLHLcLkiR+tnIpZ3rk7d2EQ6R1jwlXnalMQ==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.750.0.tgz", + "integrity": "sha512-FGYrDjXN/FOQVi/t8fHSv8zCk+NEvtFnuc4cZUj5OIbM4vrfFc5VaPyn41Uza3iv6Qq9rZg0QOwWnqK8lNrqUw==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/client-sso": "3.750.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/token-providers": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.750.0.tgz", + "integrity": "sha512-Nz8zs3YJ+GOTSrq+LyzbbC1Ffpt7pK38gcOyNZv76pP5MswKTUKNYBJehqwa+i7FcFQHsCk3TdhR8MT1ZR23uA==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/nested-clients": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.734.0.tgz", + "integrity": "sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.734.0.tgz", + "integrity": "sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.734.0.tgz", + "integrity": "sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.750.0.tgz", + "integrity": "sha512-YYcslDsP5+2NZoN3UwuhZGkhAHPSli7HlJHBafBrvjGV/I9f8FuOO1d1ebxGdEP4HyRXUGyh+7Ur4q+Psk0ryw==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@smithy/core": "^3.1.4", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.750.0.tgz", + "integrity": "sha512-OH68BRF0rt9nDloq4zsfeHI0G21lj11a66qosaljtEP66PWm7tQ06feKbFkXHT5E1K3QhJW3nVyK8v2fEBY5fg==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.750.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.4", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/middleware-retry": "^4.0.6", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.6", + "@smithy/util-defaults-mode-node": "^4.0.6", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.734.0.tgz", + "integrity": "sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.750.0.tgz", + "integrity": "sha512-X/KzqZw41iWolwNdc8e3RMcNSMR364viHv78u6AefXOO5eRM40c4/LuST1jDzq35/LpnqRhL7/MuixOetw+sFw==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/nested-clients": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.734.0.tgz", + "integrity": "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.743.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.743.0.tgz", + "integrity": "sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "@smithy/util-endpoints": "^3.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz", + "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.734.0.tgz", + "integrity": "sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.750.0.tgz", + "integrity": "sha512-84HJj9G9zbrHX2opLk9eHfDceB+UIHVrmflMzWHpsmo9fDuro/flIBqaVDlE021Osj6qIM0SJJcnL6s23j7JEw==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", "cpu": [ - "s390x" + "ppc64" ], "dev": true, "optional": true, "os": [ - "linux" + "aix" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-x64": { + "node_modules/@esbuild/android-arm": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", - "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", "cpu": [ - "x64" + "arm" ], "dev": true, "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ - "netbsd" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-x64": { + "node_modules/@esbuild/android-x64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", "cpu": [ "x64" ], "dev": true, "optional": true, "os": [ - "netbsd" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-arm64": { + "node_modules/@esbuild/darwin-arm64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", "cpu": [ "arm64" ], "dev": true, "optional": true, "os": [ - "openbsd" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-x64": { + "node_modules/@esbuild/darwin-x64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", "cpu": [ "x64" ], "dev": true, "optional": true, "os": [ - "openbsd" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/sunos-x64": { + "node_modules/@esbuild/freebsd-arm64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", "cpu": [ - "x64" + "arm64" ], "dev": true, "optional": true, "os": [ - "sunos" + "freebsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-arm64": { + "node_modules/@esbuild/freebsd-x64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", - "cpu": [ + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ "arm64" ], "dev": true, @@ -1567,175 +2268,868 @@ "x64" ], "optional": true, - "os": [ - "freebsd" - ] + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", + "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", + "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", + "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", + "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", + "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", + "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", + "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", + "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", + "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", + "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", + "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", + "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", + "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", + "integrity": "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.0.1.tgz", + "integrity": "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.4.tgz", + "integrity": "sha512-wFExFGK+7r2wYriOqe7RRIBNpvxwiS95ih09+GSLRBdoyK/O1uZA7K7pKesj5CBvwJuSBeXwLyR88WwIAY+DGA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/middleware-serde": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-stream": "^4.1.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.1.tgz", + "integrity": "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.1.tgz", + "integrity": "sha512-Q2bCAAR6zXNVtJgifsU16ZjKGqdw/DyecKNgIgi7dlqw04fqDu0mnq+JmGphqheypVc64CYq3azSuCpAdFk2+A==", + "optional": true, + "peer": true, + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.1.0", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.1.tgz", + "integrity": "sha512-HbIybmz5rhNg+zxKiyVAnvdM3vkzjE6ccrJ620iPL8IXcJEntd3hnBl+ktMwIy12Te/kyrSbUb8UCdnUT4QEdA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.0.1.tgz", + "integrity": "sha512-lSipaiq3rmHguHa3QFF4YcCM3VJOrY9oq2sow3qlhFY+nBSTF/nrO82MUQRPrxHQXA58J5G1UnU2WuJfi465BA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.1.tgz", + "integrity": "sha512-o4CoOI6oYGYJ4zXo34U8X9szDe3oGjmHgsMGiZM0j4vtNoT+h80TLnkUcrLZR3+E6HIxqW+G+9WHAVfl0GXK0Q==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.1.tgz", + "integrity": "sha512-Z94uZp0tGJuxds3iEAZBqGU2QiaBHP4YytLUjwZWx+oUeohCsLyUm33yp4MMBmhkuPqSbQCXq5hDet6JGUgHWA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/eventstream-codec": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.1.tgz", + "integrity": "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.1.tgz", + "integrity": "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.1.tgz", + "integrity": "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.1.tgz", + "integrity": "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.5.tgz", + "integrity": "sha512-cPzGZV7qStHwboFrm6GfrzQE+YDiCzWcTh4+7wKrP/ZQ4gkw+r7qDjV8GjM4N0UYsuUyLfpzLGg5hxsYTU11WA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/core": "^3.1.4", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-middleware": "^4.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.6.tgz", + "integrity": "sha512-s8QzuOQnbdvRymD9Gt9c9zMq10wUQAHQ3z72uirrBHCwZcLTrL5iCOuVTMdka2IXOYhQE890WD5t6G24+F+Qcg==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.2.tgz", + "integrity": "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.1.tgz", + "integrity": "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.1.tgz", + "integrity": "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.2.tgz", + "integrity": "sha512-X66H9aah9hisLLSnGuzRYba6vckuFtGE+a5DcHLliI/YlqKrGoxhisD5XbX44KyoeRzoNlGr94eTsMVHFAzPOw==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/abort-controller": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.1.tgz", + "integrity": "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.0.1.tgz", + "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.1.tgz", + "integrity": "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.1.tgz", + "integrity": "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.1.tgz", + "integrity": "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.1.tgz", + "integrity": "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.1.tgz", + "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.5.tgz", + "integrity": "sha512-DMXYoYeL4QkElr216n1yodTFeATbfb4jwYM9gKn71Rw/FNA1/Sm36tkTSCsZEs7mgpG3OINmkxL9vgVFzyGPaw==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/core": "^3.1.4", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", + "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", - "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", - "cpu": [ - "arm" - ], + "node_modules/@smithy/url-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", + "integrity": "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@smithy/querystring-parser": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", - "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", - "cpu": [ - "arm" - ], + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", - "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", - "cpu": [ - "arm64" - ], + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", - "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", - "cpu": [ - "arm64" - ], + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", - "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", - "cpu": [ - "loong64" - ], + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", - "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", - "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", - "cpu": [ - "riscv64" - ], + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.6.tgz", + "integrity": "sha512-N8+VCt+piupH1A7DgSVDNrVHqRLz8r6DvBkpS7EWHiIxsUk4jqGuQLjqC/gnCzmwGkVBdNruHoYAzzaSQ8e80w==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", - "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", - "cpu": [ - "s390x" - ], + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.6.tgz", + "integrity": "sha512-9zhx1shd1VwSSVvLZB8CM3qQ3RPD3le7A3h/UPuyh/PC7g4OaWDi2xUNzamsVoSmCGtmUBONl56lM2EU6LcH7A==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@smithy/config-resolver": "^4.0.1", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", - "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", - "cpu": [ - "x64" - ], + "node_modules/@smithy/util-endpoints": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.1.tgz", + "integrity": "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", - "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", - "cpu": [ - "x64" - ], + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", - "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", - "cpu": [ - "arm64" - ], + "node_modules/@smithy/util-middleware": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", + "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", "optional": true, - "os": [ - "win32" - ] + "peer": true, + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", - "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", - "cpu": [ - "ia32" - ], + "node_modules/@smithy/util-retry": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.1.tgz", + "integrity": "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==", "optional": true, - "os": [ - "win32" - ] + "peer": true, + "dependencies": { + "@smithy/service-error-classification": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", - "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", - "cpu": [ - "x64" - ], + "node_modules/@smithy/util-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.1.tgz", + "integrity": "sha512-+Xvh8nhy0Wjv1y71rBVyV3eJU3356XsFQNI8dEZVNrQju7Eib8G31GWtO+zMa9kTCGd41Mflu+ZKfmQL/o2XzQ==", "optional": true, - "os": [ - "win32" - ] + "peer": true, + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/types": "^4.1.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=10" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "optional": true, + "peer": true, + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, "node_modules/@streamparser/json": { @@ -2355,6 +3749,7 @@ "version": "0.0.61", "resolved": "https://registry.npmjs.org/bee-agent-framework/-/bee-agent-framework-0.0.61.tgz", "integrity": "sha512-SnKlcPgBMQ9pJ83yso6CLnODme3Pzsu3Yc3fFlOalPRRfxgz75yo9+wIiCUQFFcGgx7UDBB4N44Bl5mfYdSk6w==", + "deprecated": "WARNING: This project has been renamed to beeai-framework. Install using beeai-framework instead.", "dependencies": { "@ai-zen/node-fetch-event-source": "^2.1.4", "@opentelemetry/api": "^1.9.0", @@ -2500,6 +3895,13 @@ "node": ">= 0.8.0" } }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "optional": true, + "peer": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3696,9 +5098,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", "devOptional": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -4168,9 +5570,9 @@ "dev": true }, "node_modules/json-schema-to-typescript": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.3.tgz", - "integrity": "sha512-iOKdzTUWEVM4nlxpFudFsWyUiu/Jakkga4OZPEt7CGoSEsAsUgdOZqR6pcgx2STBek9Gm4hcarJpXSzIvZ/hKA==", + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", + "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.5", "@types/json-schema": "^7.0.15", @@ -6027,21 +7429,24 @@ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==" }, "node_modules/tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.11.tgz", + "integrity": "sha512-32TmKeeKUahv0Go8WmQgiEp9Y21NuxjwjqiRC1nrUB51YacfSwuB44xgXD+HdIppmMRgjQNPdrHyA6vIybYZ+g==", "dependencies": { - "fdir": "^6.4.2", + "fdir": "^6.4.3", "picomatch": "^4.0.2" }, "engines": { "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", - "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -7189,19 +8594,19 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.23.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz", - "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz", + "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", "peerDependencies": { - "zod": "^3.23.3" + "zod": "^3.24.1" } } } diff --git a/package.json b/package.json index 976fa8a..81b548c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "bee-empty", + "name": "bee-supervisor", "type": "module", "version": "0.0.1", "private": true, @@ -10,21 +10,19 @@ "files": [ "dist/**/*" ], - "main": "./dist/agent.js", - "types": "./dist/agent.d.ts", - "homepage": "https://github.com/i-am-bee/bee-agent-framework-starter#readme", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "homepage": "https://github.com/aleskalfas/bee-supervisor-poc#readme", "repository": { "type": "git", - "url": "git+https://github.com/i-am-bee/bee-agent-framework-starter.git" + "url": "git+https://github.com/aleskalfas/bee-supervisor-poc.git" }, "bugs": { - "url": "https://github.com/i-am-bee/bee-agent-framework-starter/issues" + "url": "https://github.com/aleskalfas/bee-supervisor-poc/issues" }, "scripts": { - "start": "NODE_ENV=development npm run startx", - "startx": "chmod +x ./start.sh && ./start.sh", "start:dev": "tsx --inspect --no-warnings src/main.js", - "monitor": "tsx --no-warnings src/ui/supervisor-monitor.js", + "monitor": "tsx --no-warnings src/ui/main.js", "ts:check": "tsc --noEmit --project tsconfig.json", "build": "rimraf dist && tsc", "lint": "eslint", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..455fa63 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6552 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + "@google-cloud/vertexai": + specifier: ^1.9.2 + version: 1.9.3 + "@ibm-generative-ai/node-sdk": + specifier: ^3.2.4 + version: 3.2.4 + "@opentelemetry/sdk-node": + specifier: ^0.57.0 + version: 0.57.2(@opentelemetry/api@1.9.0) + "@types/blessed": + specifier: ^0.1.25 + version: 0.1.25 + bee-agent-framework: + specifier: ^0.0.61 + version: 0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0) + bee-observe-connector: + specifier: ^0.0.6 + version: 0.0.6(bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0)) + blessed: + specifier: ^0.1.81 + version: 0.1.81 + chokidar: + specifier: ^4.0.3 + version: 4.0.3 + dotenv: + specifier: ^16.4.5 + version: 16.4.7 + groq-sdk: + specifier: ^0.7.0 + version: 0.7.0 + ollama: + specifier: ^0.5.11 + version: 0.5.13 + openai: + specifier: ^4.77.0 + version: 4.85.2(zod@3.24.2) + openai-chat-tokens: + specifier: ^0.2.8 + version: 0.2.8 + pino: + specifier: ^9.4.0 + version: 9.6.0 + remeda: + specifier: ^2.20.1 + version: 2.20.2 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.5.4)(vite@6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0)) + vitest: + specifier: ^3.0.5 + version: 3.0.6(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0) + zod: + specifier: ^3.23.8 + version: 3.24.2 + devDependencies: + "@eslint/js": + specifier: ^9.10.0 + version: 9.20.0 + "@types/eslint-config-prettier": + specifier: ^6.11.3 + version: 6.11.3 + "@types/eslint__js": + specifier: ^8.42.3 + version: 8.42.3 + "@types/node": + specifier: ^18.15.3 + version: 18.19.76 + eslint: + specifier: ^9.10.0 + version: 9.20.1 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@9.20.1) + husky: + specifier: ^9.1.6 + version: 9.1.7 + lint-staged: + specifier: ^15.2.10 + version: 15.4.3 + pino-pretty: + specifier: ^11.2.2 + version: 11.3.0 + prettier: + specifier: ^3.3.3 + version: 3.5.1 + rimraf: + specifier: ^5.0.10 + version: 5.0.10 + tsx: + specifier: ^4.19.1 + version: 4.19.3 + typescript: + specifier: ~5.5.4 + version: 5.5.4 + typescript-eslint: + specifier: ^8.6.0 + version: 8.24.1(eslint@9.20.1)(typescript@5.5.4) + +packages: + "@ai-zen/node-fetch-event-source@2.1.4": + resolution: + { + integrity: sha512-OHFwPJecr+qwlyX5CGmTvKAKPZAdZaxvx/XDqS1lx4I2ZAk9riU0XnEaRGOOAEFrdcLZ98O5yWqubwjaQc0umg==, + } + + "@apidevtools/json-schema-ref-parser@11.9.1": + resolution: + { + integrity: sha512-OvyhwtYaWSTfo8NfibmFlgl+pIMaBOmN0OwZ3CPaGscEK3B8FCVDuQ7zgxY8seU/1kfSvNWnyB0DtKJyNLxX7g==, + } + engines: { node: ">= 16" } + + "@babel/runtime@7.26.9": + resolution: + { + integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==, + } + engines: { node: ">=6.9.0" } + + "@esbuild/aix-ppc64@0.24.2": + resolution: + { + integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/aix-ppc64@0.25.0": + resolution: + { + integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/android-arm64@0.24.2": + resolution: + { + integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm64@0.25.0": + resolution: + { + integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm@0.24.2": + resolution: + { + integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-arm@0.25.0": + resolution: + { + integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-x64@0.24.2": + resolution: + { + integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/android-x64@0.25.0": + resolution: + { + integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/darwin-arm64@0.24.2": + resolution: + { + integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-arm64@0.25.0": + resolution: + { + integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-x64@0.24.2": + resolution: + { + integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/darwin-x64@0.25.0": + resolution: + { + integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/freebsd-arm64@0.24.2": + resolution: + { + integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-arm64@0.25.0": + resolution: + { + integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.24.2": + resolution: + { + integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.25.0": + resolution: + { + integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/linux-arm64@0.24.2": + resolution: + { + integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm64@0.25.0": + resolution: + { + integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm@0.24.2": + resolution: + { + integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-arm@0.25.0": + resolution: + { + integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-ia32@0.24.2": + resolution: + { + integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-ia32@0.25.0": + resolution: + { + integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-loong64@0.24.2": + resolution: + { + integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-loong64@0.25.0": + resolution: + { + integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-mips64el@0.24.2": + resolution: + { + integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-mips64el@0.25.0": + resolution: + { + integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-ppc64@0.24.2": + resolution: + { + integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-ppc64@0.25.0": + resolution: + { + integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-riscv64@0.24.2": + resolution: + { + integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-riscv64@0.25.0": + resolution: + { + integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-s390x@0.24.2": + resolution: + { + integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-s390x@0.25.0": + resolution: + { + integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-x64@0.24.2": + resolution: + { + integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/linux-x64@0.25.0": + resolution: + { + integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/netbsd-arm64@0.24.2": + resolution: + { + integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-arm64@0.25.0": + resolution: + { + integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.24.2": + resolution: + { + integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.25.0": + resolution: + { + integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.24.2": + resolution: + { + integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-arm64@0.25.0": + resolution: + { + integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.24.2": + resolution: + { + integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.25.0": + resolution: + { + integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/sunos-x64@0.24.2": + resolution: + { + integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/sunos-x64@0.25.0": + resolution: + { + integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/win32-arm64@0.24.2": + resolution: + { + integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-arm64@0.25.0": + resolution: + { + integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-ia32@0.24.2": + resolution: + { + integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-ia32@0.25.0": + resolution: + { + integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-x64@0.24.2": + resolution: + { + integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@esbuild/win32-x64@0.25.0": + resolution: + { + integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@eslint-community/eslint-utils@4.4.1": + resolution: + { + integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + "@eslint-community/regexpp@4.12.1": + resolution: + { + integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + "@eslint/config-array@0.19.2": + resolution: + { + integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/core@0.11.0": + resolution: + { + integrity: sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/eslintrc@3.2.0": + resolution: + { + integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/js@9.20.0": + resolution: + { + integrity: sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/object-schema@2.1.6": + resolution: + { + integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/plugin-kit@0.2.6": + resolution: + { + integrity: sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@google-cloud/vertexai@1.9.3": + resolution: + { + integrity: sha512-35o5tIEMLW3JeFJOaaMNR2e5sq+6rpnhrF97PuAxeOm0GlqVTESKhkGj7a5B5mmJSSSU3hUfIhcQCRRsw4Ipzg==, + } + engines: { node: ">=18.0.0" } + + "@grpc/grpc-js@1.12.6": + resolution: + { + integrity: sha512-JXUj6PI0oqqzTGvKtzOkxtpsyPRNsrmhh41TtIz/zEB6J+AUiZZ0dxWzcMwO9Ns5rmSPuMdghlTbUuqIM48d3Q==, + } + engines: { node: ">=12.10.0" } + + "@grpc/proto-loader@0.7.13": + resolution: + { + integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==, + } + engines: { node: ">=6" } + hasBin: true + + "@humanfs/core@0.19.1": + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, + } + engines: { node: ">=18.18.0" } + + "@humanfs/node@0.16.6": + resolution: + { + integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==, + } + engines: { node: ">=18.18.0" } + + "@humanwhocodes/module-importer@1.0.1": + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: ">=12.22" } + + "@humanwhocodes/retry@0.3.1": + resolution: + { + integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==, + } + engines: { node: ">=18.18" } + + "@humanwhocodes/retry@0.4.2": + resolution: + { + integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==, + } + engines: { node: ">=18.18" } + + "@ibm-generative-ai/node-sdk@3.2.4": + resolution: + { + integrity: sha512-HvJSYql3lOPYZcGb23mBw0kcWLlCX+n7EDRgJQxz7gIzx9WafUuDyl1IlTCXGfxolm0EhNIub79u9v7owtks0w==, + } + peerDependencies: + "@langchain/core": ">=0.1.0" + peerDependenciesMeta: + "@langchain/core": + optional: true + + "@isaacs/cliui@8.0.2": + resolution: + { + integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, + } + engines: { node: ">=12" } + + "@jridgewell/sourcemap-codec@1.5.0": + resolution: + { + integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==, + } + + "@js-sdsl/ordered-map@4.4.2": + resolution: + { + integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==, + } + + "@jsdevtools/ono@7.1.3": + resolution: + { + integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==, + } + + "@mixmark-io/domino@2.2.0": + resolution: + { + integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==, + } + + "@nodelib/fs.scandir@2.1.5": + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: ">= 8" } + + "@nodelib/fs.stat@2.0.5": + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: ">= 8" } + + "@nodelib/fs.walk@1.2.8": + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: ">= 8" } + + "@opentelemetry/api-logs@0.57.2": + resolution: + { + integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==, + } + engines: { node: ">=14" } + + "@opentelemetry/api@1.9.0": + resolution: + { + integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==, + } + engines: { node: ">=8.0.0" } + + "@opentelemetry/context-async-hooks@1.30.1": + resolution: + { + integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/core@1.30.1": + resolution: + { + integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/exporter-logs-otlp-grpc@0.57.2": + resolution: + { + integrity: sha512-eovEy10n3umjKJl2Ey6TLzikPE+W4cUQ4gCwgGP1RqzTGtgDra0WjIqdy29ohiUKfvmbiL3MndZww58xfIvyFw==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-logs-otlp-http@0.57.2": + resolution: + { + integrity: sha512-0rygmvLcehBRp56NQVLSleJ5ITTduq/QfU7obOkyWgPpFHulwpw2LYTqNIz5TczKZuy5YY+5D3SDnXZL1tXImg==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-logs-otlp-proto@0.57.2": + resolution: + { + integrity: sha512-ta0ithCin0F8lu9eOf4lEz9YAScecezCHkMMyDkvd9S7AnZNX5ikUmC5EQOQADU+oCcgo/qkQIaKcZvQ0TYKDw==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-metrics-otlp-grpc@0.57.2": + resolution: + { + integrity: sha512-r70B8yKR41F0EC443b5CGB4rUaOMm99I5N75QQt6sHKxYDzSEc6gm48Diz1CI1biwa5tDPznpylTrywO/pT7qw==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-metrics-otlp-http@0.57.2": + resolution: + { + integrity: sha512-ttb9+4iKw04IMubjm3t0EZsYRNWr3kg44uUuzfo9CaccYlOh8cDooe4QObDUkvx9d5qQUrbEckhrWKfJnKhemA==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-metrics-otlp-proto@0.57.2": + resolution: + { + integrity: sha512-HX068Q2eNs38uf7RIkNN9Hl4Ynl+3lP0++KELkXMCpsCbFO03+0XNNZ1SkwxPlP9jrhQahsMPMkzNXpq3fKsnw==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-prometheus@0.57.2": + resolution: + { + integrity: sha512-VqIqXnuxWMWE/1NatAGtB1PvsQipwxDcdG4RwA/umdBcW3/iOHp0uejvFHTRN2O78ZPged87ErJajyUBPUhlDQ==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-trace-otlp-grpc@0.57.2": + resolution: + { + integrity: sha512-gHU1vA3JnHbNxEXg5iysqCWxN9j83d7/epTYBZflqQnTyCC4N7yZXn/dMM+bEmyhQPGjhCkNZLx4vZuChH1PYw==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-trace-otlp-http@0.57.2": + resolution: + { + integrity: sha512-sB/gkSYFu+0w2dVQ0PWY9fAMl172PKMZ/JrHkkW8dmjCL0CYkmXeE+ssqIL/yBUTPOvpLIpenX5T9RwXRBW/3g==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-trace-otlp-proto@0.57.2": + resolution: + { + integrity: sha512-awDdNRMIwDvUtoRYxRhja5QYH6+McBLtoz1q9BeEsskhZcrGmH/V1fWpGx8n+Rc+542e8pJA6y+aullbIzQmlw==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/exporter-zipkin@1.30.1": + resolution: + { + integrity: sha512-6S2QIMJahIquvFaaxmcwpvQQRD/YFaMTNoIxrfPIPOeITN+a8lfEcPDxNxn8JDAaxkg+4EnXhz8upVDYenoQjA==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.0.0 + + "@opentelemetry/instrumentation@0.57.2": + resolution: + { + integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/otlp-exporter-base@0.57.2": + resolution: + { + integrity: sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/otlp-grpc-exporter-base@0.57.2": + resolution: + { + integrity: sha512-USn173KTWy0saqqRB5yU9xUZ2xdgb1Rdu5IosJnm9aV4hMTuFFRTUsQxbgc24QxpCHeoKzzCSnS/JzdV0oM2iQ==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/otlp-transformer@0.57.2": + resolution: + { + integrity: sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/propagator-b3@1.30.1": + resolution: + { + integrity: sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/propagator-jaeger@1.30.1": + resolution: + { + integrity: sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/resources@1.30.1": + resolution: + { + integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/sdk-logs@0.57.2": + resolution: + { + integrity: sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.4.0 <1.10.0" + + "@opentelemetry/sdk-metrics@1.30.1": + resolution: + { + integrity: sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + + "@opentelemetry/sdk-node@0.57.2": + resolution: + { + integrity: sha512-8BaeqZyN5sTuPBtAoY+UtKwXBdqyuRKmekN5bFzAO40CgbGzAxfTpiL3PBerT7rhZ7p2nBdq7FaMv/tBQgHE4A==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + + "@opentelemetry/sdk-trace-base@1.30.1": + resolution: + { + integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/sdk-trace-node@1.30.1": + resolution: + { + integrity: sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/semantic-conventions@1.28.0": + resolution: + { + integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==, + } + engines: { node: ">=14" } + + "@pkgjs/parseargs@0.11.0": + resolution: + { + integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, + } + engines: { node: ">=14" } + + "@protobufjs/aspromise@1.1.2": + resolution: + { + integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==, + } + + "@protobufjs/base64@1.1.2": + resolution: + { + integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==, + } + + "@protobufjs/codegen@2.0.4": + resolution: + { + integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==, + } + + "@protobufjs/eventemitter@1.1.0": + resolution: + { + integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==, + } + + "@protobufjs/fetch@1.1.0": + resolution: + { + integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==, + } + + "@protobufjs/float@1.0.2": + resolution: + { + integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==, + } + + "@protobufjs/inquire@1.1.0": + resolution: + { + integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==, + } + + "@protobufjs/path@1.1.2": + resolution: + { + integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==, + } + + "@protobufjs/pool@1.1.0": + resolution: + { + integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==, + } + + "@protobufjs/utf8@1.1.0": + resolution: + { + integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==, + } + + "@rollup/rollup-android-arm-eabi@4.34.8": + resolution: + { + integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==, + } + cpu: [arm] + os: [android] + + "@rollup/rollup-android-arm64@4.34.8": + resolution: + { + integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==, + } + cpu: [arm64] + os: [android] + + "@rollup/rollup-darwin-arm64@4.34.8": + resolution: + { + integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==, + } + cpu: [arm64] + os: [darwin] + + "@rollup/rollup-darwin-x64@4.34.8": + resolution: + { + integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==, + } + cpu: [x64] + os: [darwin] + + "@rollup/rollup-freebsd-arm64@4.34.8": + resolution: + { + integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==, + } + cpu: [arm64] + os: [freebsd] + + "@rollup/rollup-freebsd-x64@4.34.8": + resolution: + { + integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==, + } + cpu: [x64] + os: [freebsd] + + "@rollup/rollup-linux-arm-gnueabihf@4.34.8": + resolution: + { + integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==, + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm-musleabihf@4.34.8": + resolution: + { + integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==, + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm64-gnu@4.34.8": + resolution: + { + integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==, + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-arm64-musl@4.34.8": + resolution: + { + integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==, + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-loongarch64-gnu@4.34.8": + resolution: + { + integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==, + } + cpu: [loong64] + os: [linux] + + "@rollup/rollup-linux-powerpc64le-gnu@4.34.8": + resolution: + { + integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==, + } + cpu: [ppc64] + os: [linux] + + "@rollup/rollup-linux-riscv64-gnu@4.34.8": + resolution: + { + integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==, + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-s390x-gnu@4.34.8": + resolution: + { + integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==, + } + cpu: [s390x] + os: [linux] + + "@rollup/rollup-linux-x64-gnu@4.34.8": + resolution: + { + integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==, + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-linux-x64-musl@4.34.8": + resolution: + { + integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==, + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-win32-arm64-msvc@4.34.8": + resolution: + { + integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==, + } + cpu: [arm64] + os: [win32] + + "@rollup/rollup-win32-ia32-msvc@4.34.8": + resolution: + { + integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==, + } + cpu: [ia32] + os: [win32] + + "@rollup/rollup-win32-x64-msvc@4.34.8": + resolution: + { + integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==, + } + cpu: [x64] + os: [win32] + + "@sindresorhus/is@4.6.0": + resolution: + { + integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==, + } + engines: { node: ">=10" } + + "@streamparser/json@0.0.21": + resolution: + { + integrity: sha512-v+49JBiG1kmc/9Ug79Lz9wyKaRocBgCnpRaLpdy7p0d3ICKtOAfc/H/Epa1j3F6YdnzjnZKKrnJ8xnh/v1P8Aw==, + } + + "@types/blessed@0.1.25": + resolution: + { + integrity: sha512-kQsjBgtsbJLmG6CJA+Z6Nujj+tq1fcSE3UIowbDvzQI4wWmoTV7djUDhSo5lDjgwpIN0oRvks0SA5mMdKE5eFg==, + } + + "@types/eslint-config-prettier@6.11.3": + resolution: + { + integrity: sha512-3wXCiM8croUnhg9LdtZUJQwNcQYGWxxdOWDjPe1ykCqJFPVpzAKfs/2dgSoCtAvdPeaponcWPI7mPcGGp9dkKQ==, + } + + "@types/eslint@9.6.1": + resolution: + { + integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==, + } + + "@types/eslint__js@8.42.3": + resolution: + { + integrity: sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==, + } + + "@types/estree@1.0.6": + resolution: + { + integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==, + } + + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + "@types/lodash-es@4.17.12": + resolution: + { + integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==, + } + + "@types/lodash@4.17.15": + resolution: + { + integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==, + } + + "@types/node-fetch@2.6.12": + resolution: + { + integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==, + } + + "@types/node@18.19.76": + resolution: + { + integrity: sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==, + } + + "@types/shimmer@1.2.0": + resolution: + { + integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==, + } + + "@typescript-eslint/eslint-plugin@8.24.1": + resolution: + { + integrity: sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + + "@typescript-eslint/parser@8.24.1": + resolution: + { + integrity: sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + + "@typescript-eslint/scope-manager@8.24.1": + resolution: + { + integrity: sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/type-utils@8.24.1": + resolution: + { + integrity: sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + + "@typescript-eslint/types@8.24.1": + resolution: + { + integrity: sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/typescript-estree@8.24.1": + resolution: + { + integrity: sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <5.8.0" + + "@typescript-eslint/utils@8.24.1": + resolution: + { + integrity: sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + + "@typescript-eslint/visitor-keys@8.24.1": + resolution: + { + integrity: sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@vitest/expect@3.0.6": + resolution: + { + integrity: sha512-zBduHf/ja7/QRX4HdP1DSq5XrPgdN+jzLOwaTq/0qZjYfgETNFCKf9nOAp2j3hmom3oTbczuUzrzg9Hafh7hNg==, + } + + "@vitest/mocker@3.0.6": + resolution: + { + integrity: sha512-KPztr4/tn7qDGZfqlSPQoF2VgJcKxnDNhmfR3VgZ6Fy1bO8T9Fc1stUiTXtqz0yG24VpD00pZP5f8EOFknjNuQ==, + } + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + "@vitest/pretty-format@3.0.6": + resolution: + { + integrity: sha512-Zyctv3dbNL+67qtHfRnUE/k8qxduOamRfAL1BurEIQSyOEFffoMvx2pnDSSbKAAVxY0Ej2J/GH2dQKI0W2JyVg==, + } + + "@vitest/runner@3.0.6": + resolution: + { + integrity: sha512-JopP4m/jGoaG1+CBqubV/5VMbi7L+NQCJTu1J1Pf6YaUbk7bZtaq5CX7p+8sY64Sjn1UQ1XJparHfcvTTdu9cA==, + } + + "@vitest/snapshot@3.0.6": + resolution: + { + integrity: sha512-qKSmxNQwT60kNwwJHMVwavvZsMGXWmngD023OHSgn873pV0lylK7dwBTfYP7e4URy5NiBCHHiQGA9DHkYkqRqg==, + } + + "@vitest/spy@3.0.6": + resolution: + { + integrity: sha512-HfOGx/bXtjy24fDlTOpgiAEJbRfFxoX3zIGagCqACkFKKZ/TTOE6gYMKXlqecvxEndKFuNHcHqP081ggZ2yM0Q==, + } + + "@vitest/utils@3.0.6": + resolution: + { + integrity: sha512-18ktZpf4GQFTbf9jK543uspU03Q2qya7ZGya5yiZ0Gx0nnnalBvd5ZBislbl2EhLjM8A8rt4OilqKG7QwcGkvQ==, + } + + abort-controller@3.0.0: + resolution: + { + integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==, + } + engines: { node: ">=6.5" } + + acorn-import-attributes@1.9.5: + resolution: + { + integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==, + } + peerDependencies: + acorn: ^8 + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.0: + resolution: + { + integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==, + } + engines: { node: ">=0.4.0" } + hasBin: true + + adm-zip@0.5.16: + resolution: + { + integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==, + } + engines: { node: ">=12.0" } + + agent-base@7.1.3: + resolution: + { + integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==, + } + engines: { node: ">= 14" } + + agentkeepalive@4.6.0: + resolution: + { + integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==, + } + engines: { node: ">= 8.0.0" } + + ajv-formats@3.0.1: + resolution: + { + integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==, + } + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, + } + + ajv@8.17.1: + resolution: + { + integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==, + } + + ansi-escapes@7.0.0: + resolution: + { + integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==, + } + engines: { node: ">=18" } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: ">=8" } + + ansi-regex@6.1.0: + resolution: + { + integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==, + } + engines: { node: ">=12" } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: ">=8" } + + ansi-styles@6.2.1: + resolution: + { + integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==, + } + engines: { node: ">=12" } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + assertion-error@2.0.1: + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, + } + engines: { node: ">=12" } + + asynckit@0.4.0: + resolution: + { + integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, + } + + atomic-sleep@1.0.0: + resolution: + { + integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==, + } + engines: { node: ">=8.0.0" } + + axios@1.7.9: + resolution: + { + integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==, + } + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + base64-js@1.5.1: + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + } + + bee-agent-framework@0.0.61: + resolution: + { + integrity: sha512-SnKlcPgBMQ9pJ83yso6CLnODme3Pzsu3Yc3fFlOalPRRfxgz75yo9+wIiCUQFFcGgx7UDBB4N44Bl5mfYdSk6w==, + } + deprecated: "WARNING: This project has been renamed to beeai-framework. Install using beeai-framework instead." + peerDependencies: + "@aws-sdk/client-bedrock-runtime": ^3.687.0 + "@elastic/elasticsearch": ^8.0.0 + "@google-cloud/vertexai": "*" + "@googleapis/customsearch": ^3.2.0 + "@grpc/grpc-js": ^1.11.3 + "@grpc/proto-loader": ^0.7.13 + "@langchain/community": ">=0.2.28" + "@langchain/core": ">=0.2.27" + "@modelcontextprotocol/sdk": ^1.0.4 + "@zilliz/milvus2-sdk-node": ^2.4.9 + google-auth-library: "*" + groq-sdk: ^0.7.0 + ollama: ^0.5.11 + openai: ^4.67.3 + openai-chat-tokens: ^0.2.8 + sequelize: ^6.37.3 + yaml: ^2.6.1 + peerDependenciesMeta: + "@aws-sdk/client-bedrock-runtime": + optional: true + "@elastic/elasticsearch": + optional: true + "@google-cloud/vertexai": + optional: true + "@googleapis/customsearch": + optional: true + "@grpc/grpc-js": + optional: true + "@grpc/proto-loader": + optional: true + "@langchain/community": + optional: true + "@langchain/core": + optional: true + "@modelcontextprotocol/sdk": + optional: true + "@zilliz/milvus2-sdk-node": + optional: true + google-auth-library: + optional: true + groq-sdk: + optional: true + ollama: + optional: true + openai: + optional: true + openai-chat-tokens: + optional: true + sequelize: + optional: true + yaml: + optional: true + + bee-observe-connector@0.0.6: + resolution: + { + integrity: sha512-KZhhOFwvw/dR2YN4DPf7dZkquJ4O41D0WhjAsU3+RApkvBnpSm6Y/TYTa7vOZKaJzbuJ0NqRVrhPD8dCuKXFMQ==, + } + peerDependencies: + bee-agent-framework: ">=0.0.27 <0.1.0" + + bignumber.js@9.1.2: + resolution: + { + integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==, + } + + blessed@0.1.81: + resolution: + { + integrity: sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==, + } + engines: { node: ">= 0.8.0" } + hasBin: true + + brace-expansion@1.1.11: + resolution: + { + integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, + } + + brace-expansion@2.0.1: + resolution: + { + integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==, + } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: ">=8" } + + browserslist@4.24.4: + resolution: + { + integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==, + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + buffer-equal-constant-time@1.0.1: + resolution: + { + integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==, + } + + buffer@6.0.3: + resolution: + { + integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==, + } + + cac@6.7.14: + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, + } + engines: { node: ">=8" } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, + } + engines: { node: ">= 0.4" } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: ">=6" } + + camelcase@4.1.0: + resolution: + { + integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==, + } + engines: { node: ">=4" } + + caniuse-lite@1.0.30001700: + resolution: + { + integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==, + } + + chai@5.2.0: + resolution: + { + integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==, + } + engines: { node: ">=12" } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: ">=10" } + + chalk@5.4.1: + resolution: + { + integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==, + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + check-error@2.1.1: + resolution: + { + integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==, + } + engines: { node: ">= 16" } + + chokidar@4.0.3: + resolution: + { + integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==, + } + engines: { node: ">= 14.16.0" } + + cjs-module-lexer@1.4.3: + resolution: + { + integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==, + } + + cli-cursor@5.0.0: + resolution: + { + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, + } + engines: { node: ">=18" } + + cli-truncate@4.0.0: + resolution: + { + integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==, + } + engines: { node: ">=18" } + + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: ">=12" } + + codsen-utils@1.6.4: + resolution: + { + integrity: sha512-PDyvQ5f2PValmqZZIJATimcokDt4JjIev8cKbZgEOoZm+U1IJDYuLeTcxZPQdep99R/X0RIlQ6ReQgPOVnPbNw==, + } + engines: { node: ">=14.18.0" } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: ">=7.0.0" } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + colorette@2.0.20: + resolution: + { + integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, + } + + combined-stream@1.0.8: + resolution: + { + integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, + } + engines: { node: ">= 0.8" } + + commander@13.1.0: + resolution: + { + integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==, + } + engines: { node: ">=18" } + + complex.js@2.4.2: + resolution: + { + integrity: sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==, + } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } + + cross-fetch@4.1.0: + resolution: + { + integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==, + } + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: ">= 8" } + + dateformat@4.6.3: + resolution: + { + integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==, + } + + debug@4.4.0: + resolution: + { + integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==, + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.5.0: + resolution: + { + integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==, + } + + deep-eql@5.0.2: + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, + } + engines: { node: ">=6" } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } + + delayed-stream@1.0.0: + resolution: + { + integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, + } + engines: { node: ">=0.4.0" } + + dot-prop@6.0.1: + resolution: + { + integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==, + } + engines: { node: ">=10" } + + dotenv@16.4.7: + resolution: + { + integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==, + } + engines: { node: ">=12" } + + duck-duck-scrape@2.2.7: + resolution: + { + integrity: sha512-BEcglwnfx5puJl90KQfX+Q2q5vCguqyMpZcSRPBWk8OY55qWwV93+E+7DbIkrGDW4qkqPfUvtOUdi0lXz6lEMQ==, + } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, + } + engines: { node: ">= 0.4" } + + eastasianwidth@0.2.0: + resolution: + { + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, + } + + ecdsa-sig-formatter@1.0.11: + resolution: + { + integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==, + } + + electron-to-chromium@1.5.102: + resolution: + { + integrity: sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==, + } + + emoji-regex@10.4.0: + resolution: + { + integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==, + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + emoji-regex@9.2.2: + resolution: + { + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, + } + + end-of-stream@1.4.4: + resolution: + { + integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==, + } + + environment@1.1.0: + resolution: + { + integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==, + } + engines: { node: ">=18" } + + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, + } + engines: { node: ">= 0.4" } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, + } + engines: { node: ">= 0.4" } + + es-module-lexer@1.6.0: + resolution: + { + integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==, + } + + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, + } + engines: { node: ">= 0.4" } + + es-set-tostringtag@2.1.0: + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, + } + engines: { node: ">= 0.4" } + + esbuild@0.24.2: + resolution: + { + integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==, + } + engines: { node: ">=18" } + hasBin: true + + esbuild@0.25.0: + resolution: + { + integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==, + } + engines: { node: ">=18" } + hasBin: true + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, + } + engines: { node: ">=6" } + + escape-latex@1.2.0: + resolution: + { + integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==, + } + + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: ">=10" } + + eslint-config-prettier@9.1.0: + resolution: + { + integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==, + } + hasBin: true + peerDependencies: + eslint: ">=7.0.0" + + eslint-scope@8.2.0: + resolution: + { + integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.0: + resolution: + { + integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint@9.20.1: + resolution: + { + integrity: sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + hasBin: true + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: + { + integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + esquery@1.6.0: + resolution: + { + integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==, + } + engines: { node: ">=0.10" } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, + } + engines: { node: ">=4.0" } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: ">=4.0" } + + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: ">=0.10.0" } + + event-target-shim@5.0.1: + resolution: + { + integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==, + } + engines: { node: ">=6" } + + eventemitter3@5.0.1: + resolution: + { + integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==, + } + + events@3.3.0: + resolution: + { + integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==, + } + engines: { node: ">=0.8.x" } + + execa@8.0.1: + resolution: + { + integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==, + } + engines: { node: ">=16.17" } + + expect-type@1.1.0: + resolution: + { + integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==, + } + engines: { node: ">=12.0.0" } + + extend@3.0.2: + resolution: + { + integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, + } + + fast-copy@3.0.2: + resolution: + { + integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==, + } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: ">=8.6.0" } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } + + fast-redact@3.5.0: + resolution: + { + integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==, + } + engines: { node: ">=6" } + + fast-safe-stringify@2.1.1: + resolution: + { + integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==, + } + + fast-uri@3.0.6: + resolution: + { + integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==, + } + + fast-xml-parser@4.5.2: + resolution: + { + integrity: sha512-xmnYV9o0StIz/0ArdzmWTxn9oDy0lH8Z80/8X/TD2EUQKXY4DHxoT9mYBqgGIG17DgddCJtH1M6DriMbalNsAA==, + } + hasBin: true + + fastq@1.19.0: + resolution: + { + integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==, + } + + fdir@6.4.3: + resolution: + { + integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==, + } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-retry@5.0.6: + resolution: + { + integrity: sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==, + } + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: ">=16.0.0" } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: ">=8" } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: ">=10" } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, + } + engines: { node: ">=16" } + + flatted@3.3.3: + resolution: + { + integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, + } + + follow-redirects@1.15.9: + resolution: + { + integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==, + } + engines: { node: ">=4.0" } + peerDependencies: + debug: "*" + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.0: + resolution: + { + integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==, + } + engines: { node: ">=14" } + + form-data-encoder@1.7.2: + resolution: + { + integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==, + } + + form-data@4.0.2: + resolution: + { + integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==, + } + engines: { node: ">= 6" } + + formdata-node@4.4.1: + resolution: + { + integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==, + } + engines: { node: ">= 12.20" } + + fraction.js@5.2.1: + resolution: + { + integrity: sha512-Ah6t/7YCYjrPUFUFsOsRLMXAdnYM+aQwmojD2Ayb/Ezr82SwES0vuyQ8qZ3QO8n9j7W14VJuVZZet8U3bhSdQQ==, + } + engines: { node: ">= 12" } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, + } + + gaxios@6.7.1: + resolution: + { + integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==, + } + engines: { node: ">=14" } + + gcp-metadata@6.1.1: + resolution: + { + integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==, + } + engines: { node: ">=14" } + + generative-bayesian-network@2.1.62: + resolution: + { + integrity: sha512-+zq1/AHdxb+0MXF34krM/IUu/N9gI6llzQg2gf7WMfuzh0nv6xbhb8QyfL48MOJihum7wSE90+/hMXK60X+Kpw==, + } + + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, + } + engines: { node: 6.* || 8.* || >= 10.* } + + get-east-asian-width@1.3.0: + resolution: + { + integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==, + } + engines: { node: ">=18" } + + get-intrinsic@1.2.7: + resolution: + { + integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==, + } + engines: { node: ">= 0.4" } + + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, + } + engines: { node: ">= 0.4" } + + get-stream@8.0.1: + resolution: + { + integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==, + } + engines: { node: ">=16" } + + get-tsconfig@4.10.0: + resolution: + { + integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==, + } + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: ">= 6" } + + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: ">=10.13.0" } + + glob@10.4.5: + resolution: + { + integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==, + } + hasBin: true + + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + } + engines: { node: ">=18" } + + globrex@0.1.2: + resolution: + { + integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==, + } + + google-auth-library@9.15.1: + resolution: + { + integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==, + } + engines: { node: ">=14" } + + google-logging-utils@0.0.2: + resolution: + { + integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==, + } + engines: { node: ">=14" } + + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, + } + engines: { node: ">= 0.4" } + + graphemer@1.4.0: + resolution: + { + integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==, + } + + groq-sdk@0.7.0: + resolution: + { + integrity: sha512-OgPqrRtti5MjEVclR8sgBHrhSkTLdFCmi47yrEF29uJZaiCkX3s7bXpnMhq8Lwoe1f4AwgC0qGOeHXpeSgu5lg==, + } + + gtoken@7.1.0: + resolution: + { + integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==, + } + engines: { node: ">=14.0.0" } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: ">=8" } + + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, + } + engines: { node: ">= 0.4" } + + has-tostringtag@1.0.2: + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, + } + engines: { node: ">= 0.4" } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, + } + engines: { node: ">= 0.4" } + + header-generator@2.1.62: + resolution: + { + integrity: sha512-L4y1Fush4bkC/3zEurWjiwzeuekAH3HMYA508EZDmvk1wPmcbpV/mq3u3d3fxq7v4oPmaCfsRm1T5DUH19uikA==, + } + engines: { node: ">=16.0.0" } + + help-me@5.0.0: + resolution: + { + integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==, + } + + html-entities@2.5.2: + resolution: + { + integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==, + } + + http-status-codes@2.3.0: + resolution: + { + integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==, + } + + https-proxy-agent@7.0.6: + resolution: + { + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, + } + engines: { node: ">= 14" } + + human-signals@5.0.0: + resolution: + { + integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==, + } + engines: { node: ">=16.17.0" } + + humanize-ms@1.2.1: + resolution: + { + integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==, + } + + husky@9.1.7: + resolution: + { + integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==, + } + engines: { node: ">=18" } + hasBin: true + + iconv-lite@0.6.3: + resolution: + { + integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, + } + engines: { node: ">=0.10.0" } + + ieee754@1.2.1: + resolution: + { + integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, + } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: ">= 4" } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, + } + engines: { node: ">=6" } + + import-in-the-middle@1.13.0: + resolution: + { + integrity: sha512-YG86SYDtrL/Yu8JgfWb7kjQ0myLeT1whw6fs/ZHFkXFcbk9zJU9lOCsSJHpvaPumU11nN3US7NW6x1YTk+HrUA==, + } + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: ">=0.8.19" } + + infobox-parser@3.6.4: + resolution: + { + integrity: sha512-d2lTlxKZX7WsYxk9/UPt51nkmZv5tbC75SSw4hfHqZ3LpRAn6ug0oru9xI2X+S78va3aUAze3xl/UqMuwLmJUw==, + } + + is-core-module@2.16.1: + resolution: + { + integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, + } + engines: { node: ">= 0.4" } + + is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, + } + engines: { node: ">=0.10.0" } + + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: ">=8" } + + is-fullwidth-code-point@4.0.0: + resolution: + { + integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==, + } + engines: { node: ">=12" } + + is-fullwidth-code-point@5.0.0: + resolution: + { + integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==, + } + engines: { node: ">=18" } + + is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, + } + engines: { node: ">=0.10.0" } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: ">=0.12.0" } + + is-obj@2.0.0: + resolution: + { + integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==, + } + engines: { node: ">=8" } + + is-stream@2.0.1: + resolution: + { + integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, + } + engines: { node: ">=8" } + + is-stream@3.0.0: + resolution: + { + integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + jackspeak@3.4.3: + resolution: + { + integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, + } + + javascript-natural-sort@0.7.1: + resolution: + { + integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==, + } + + joplin-turndown-plugin-gfm@1.0.12: + resolution: + { + integrity: sha512-qL4+1iycQjZ1fs8zk3jSRk7cg3ROBUHk7GKtiLAQLFzLPKErnILUvz5DLszSQvz3s1sTjPbywLDISVUtBY6HaA==, + } + + joycon@3.1.1: + resolution: + { + integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==, + } + engines: { node: ">=10" } + + js-tiktoken@1.0.19: + resolution: + { + integrity: sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==, + } + + js-yaml@4.1.0: + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, + } + hasBin: true + + json-bigint@1.0.0: + resolution: + { + integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==, + } + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + + json-schema-to-typescript@15.0.4: + resolution: + { + integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==, + } + engines: { node: ">=16.0.0" } + hasBin: true + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } + + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } + + jsonrepair@3.12.0: + resolution: + { + integrity: sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w==, + } + hasBin: true + + jwa@2.0.0: + resolution: + { + integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==, + } + + jws@4.0.0: + resolution: + { + integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==, + } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: ">= 0.8.0" } + + lilconfig@3.1.3: + resolution: + { + integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==, + } + engines: { node: ">=14" } + + lint-staged@15.4.3: + resolution: + { + integrity: sha512-FoH1vOeouNh1pw+90S+cnuoFwRfUD9ijY2GKy5h7HS3OR7JVir2N2xrsa0+Twc1B7cW72L+88geG5cW4wIhn7g==, + } + engines: { node: ">=18.12.0" } + hasBin: true + + listr2@8.2.5: + resolution: + { + integrity: sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==, + } + engines: { node: ">=18.0.0" } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: ">=10" } + + lodash-es@4.17.21: + resolution: + { + integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==, + } + + lodash.camelcase@4.3.0: + resolution: + { + integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==, + } + + lodash.isequal@4.5.0: + resolution: + { + integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==, + } + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, + } + + lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, + } + + log-update@6.1.0: + resolution: + { + integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==, + } + engines: { node: ">=18" } + + long@5.3.1: + resolution: + { + integrity: sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==, + } + + loupe@3.1.3: + resolution: + { + integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==, + } + + lru-cache@10.4.3: + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, + } + + magic-string@0.30.17: + resolution: + { + integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==, + } + + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, + } + engines: { node: ">= 0.4" } + + mathjs@14.2.1: + resolution: + { + integrity: sha512-vARWETUx75+kR2K9qBV20n6NYtGXCuQKX8Zo4+AhJI5LX+ukSM1NYebv+wLnJG8KMvEe9H01sJUyC5bMciA4Tg==, + } + engines: { node: ">= 18" } + hasBin: true + + merge-stream@2.0.0: + resolution: + { + integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, + } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: ">= 8" } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: ">=8.6" } + + mime-db@1.52.0: + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, + } + engines: { node: ">= 0.6" } + + mime-types@2.1.35: + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, + } + engines: { node: ">= 0.6" } + + mimic-fn@4.0.0: + resolution: + { + integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==, + } + engines: { node: ">=12" } + + mimic-function@5.0.1: + resolution: + { + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==, + } + engines: { node: ">=18" } + + minimatch@3.1.2: + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, + } + + minimatch@9.0.5: + resolution: + { + integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, + } + engines: { node: ">=16 || 14 >=14.17" } + + minimist@1.2.8: + resolution: + { + integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, + } + + minipass@7.1.2: + resolution: + { + integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, + } + engines: { node: ">=16 || 14 >=14.17" } + + module-details-from-path@1.0.3: + resolution: + { + integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==, + } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + mustache@4.2.0: + resolution: + { + integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==, + } + hasBin: true + + nanoid@3.3.8: + resolution: + { + integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } + + needle@3.3.1: + resolution: + { + integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==, + } + engines: { node: ">= 4.4.x" } + hasBin: true + + node-domexception@1.0.0: + resolution: + { + integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==, + } + engines: { node: ">=10.5.0" } + + node-fetch@2.7.0: + resolution: + { + integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==, + } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.19: + resolution: + { + integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, + } + + npm-run-path@5.3.0: + resolution: + { + integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + object-hash@3.0.0: + resolution: + { + integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==, + } + engines: { node: ">= 6" } + + ollama@0.5.13: + resolution: + { + integrity: sha512-qK3eE2GjMYjCiTknEJfAHjbUzUqgVtf9qtzjxWrkwBZgBG7kOB6Z4+Ov4fbvDjmKKHv+rpuTsWFg4jZvVjNBtQ==, + } + + on-exit-leak-free@2.1.2: + resolution: + { + integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==, + } + engines: { node: ">=14.0.0" } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, + } + + onetime@6.0.0: + resolution: + { + integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==, + } + engines: { node: ">=12" } + + onetime@7.0.0: + resolution: + { + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==, + } + engines: { node: ">=18" } + + openai-chat-tokens@0.2.8: + resolution: + { + integrity: sha512-nW7QdFDIZlAYe6jsCT/VPJ/Lam3/w2DX9oxf/5wHpebBT49KI3TN43PPhYlq1klq2ajzXWKNOLY6U4FNZM7AoA==, + } + + openai@4.85.2: + resolution: + { + integrity: sha512-ZQg3Q+K4A6M9dLFh5W36paZkZBQO+VbxMNJ1gUSyHsGiEWuXahdn02ermqNV68LhWQxdJQaWUFRAYpW/suTPWQ==, + } + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + openapi-fetch@0.11.3: + resolution: + { + integrity: sha512-r18fERgpxFrI4pv79ABD1dqFetWz7pTfwRd7jQmRm/lFdCDpWF43kvHUiOqOZu+tWsMydDJMpJN1hlZ9inRvfA==, + } + + openapi-fetch@0.8.2: + resolution: + { + integrity: sha512-4g+NLK8FmQ51RW6zLcCBOVy/lwYmFJiiT+ckYZxJWxUxH4XFhsNcX2eeqVMfVOi+mDNFja6qDXIZAz2c5J/RVw==, + } + + openapi-typescript-helpers@0.0.13: + resolution: + { + integrity: sha512-z44WK2e7ygW3aUtAtiurfEACohf/Qt9g6BsejmIYgEoY4REHeRzgFJmO3ium0libsuzPc145I+8lE9aiiZrQvQ==, + } + + openapi-typescript-helpers@0.0.5: + resolution: + { + integrity: sha512-MRffg93t0hgGZbYTxg60hkRIK2sRuEOHEtCUgMuLgbCC33TMQ68AmxskzUlauzZYD47+ENeGV/ElI7qnWqrAxA==, + } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: ">= 0.8.0" } + + ow@0.28.2: + resolution: + { + integrity: sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==, + } + engines: { node: ">=12" } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: ">=10" } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: ">=10" } + + p-queue-compat@1.0.225: + resolution: + { + integrity: sha512-SdfGSQSJJpD7ZR+dJEjjn9GuuBizHPLW/yarJpXnmrHRruzrq7YM8OqsikSrKeoPv+Pi1YXw9IIBSIg5WveQHA==, + } + engines: { node: ">=12" } + + p-queue-compat@1.0.229: + resolution: + { + integrity: sha512-w/jxEziXYIoQ4O93LijryeEWTfcx/dTshOzPtydQ9kfgr1nPZ+h7h2nvoL/1rYYL/Rg2uwUydZV8GDkAYPqV5w==, + } + engines: { node: ">=12" } + + p-throttle@7.0.0: + resolution: + { + integrity: sha512-aio0v+S0QVkH1O+9x4dHtD4dgCExACcL+3EtNaGqC01GBudS9ijMuUsmN8OVScyV4OOp0jqdLShZFuSlbL/AsA==, + } + engines: { node: ">=18" } + + p-timeout-compat@1.0.6: + resolution: + { + integrity: sha512-4Xj6j5oP1hYuBcl3iPhY972E8gm427UGqwV4UoXQB94ezqMY8VcRPV1rwAeENtSqCNjiEkEMwPgNdPpJdZV84w==, + } + engines: { node: ">=12" } + + package-json-from-dist@1.0.1: + resolution: + { + integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, + } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: ">=6" } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: ">=8" } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: ">=8" } + + path-key@4.0.0: + resolution: + { + integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, + } + engines: { node: ">=12" } + + path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, + } + + path-scurry@1.11.1: + resolution: + { + integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==, + } + engines: { node: ">=16 || 14 >=14.18" } + + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } + + pathval@2.0.0: + resolution: + { + integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==, + } + engines: { node: ">= 14.16" } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: ">=8.6" } + + picomatch@4.0.2: + resolution: + { + integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==, + } + engines: { node: ">=12" } + + pidtree@0.6.0: + resolution: + { + integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==, + } + engines: { node: ">=0.10" } + hasBin: true + + pino-abstract-transport@2.0.0: + resolution: + { + integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==, + } + + pino-pretty@11.3.0: + resolution: + { + integrity: sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==, + } + hasBin: true + + pino-std-serializers@7.0.0: + resolution: + { + integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==, + } + + pino@9.6.0: + resolution: + { + integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==, + } + hasBin: true + + postcss@8.5.3: + resolution: + { + integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==, + } + engines: { node: ^10 || ^12 || >=14 } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: ">= 0.8.0" } + + prettier@3.5.1: + resolution: + { + integrity: sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==, + } + engines: { node: ">=14" } + hasBin: true + + process-warning@4.0.1: + resolution: + { + integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==, + } + + process@0.11.10: + resolution: + { + integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==, + } + engines: { node: ">= 0.6.0" } + + promise-based-task@3.1.1: + resolution: + { + integrity: sha512-67wj5yO49S47PxPV4vMCcqGPm/K8AMyE1uXCyRFSd9y/crUaRyJf/NHy8UMNsyWm/PEOWWOa27fXxu591/xLag==, + } + + protobufjs@7.4.0: + resolution: + { + integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==, + } + engines: { node: ">=12.0.0" } + + proxy-from-env@1.1.0: + resolution: + { + integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, + } + + pump@3.0.2: + resolution: + { + integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==, + } + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: ">=6" } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + + quick-format-unescaped@4.0.4: + resolution: + { + integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==, + } + + ranges-apply@7.0.16: + resolution: + { + integrity: sha512-4rGJHOyA7qatiMDg3vcETkc/TVBPU86/xZRTXff6o7a2neYLmj0EXUUAlhLVuiWAzTPHDPHOQxtk8EDrIF4ohg==, + } + engines: { node: ">=14.18.0" } + + ranges-merge@9.0.15: + resolution: + { + integrity: sha512-hvt4hx0FKIaVfjd1oKx0poL57ljxdL2KHC6bXBrAdsx2iCsH+x7nO/5J0k2veM/isnOcFZKp0ZKkiCjCtzy74Q==, + } + engines: { node: ">=14.18.0" } + + ranges-push@7.0.15: + resolution: + { + integrity: sha512-gXpBYQ5Umf3uG6jkJnw5ddok2Xfo5p22rAJBLrqzNKa7qkj3q5AOCoxfRPXEHUVaJutfXc9K9eGXdIzdyQKPkw==, + } + engines: { node: ">=14.18.0" } + + ranges-sort@6.0.11: + resolution: + { + integrity: sha512-fhNEG0vGi7bESitNNqNBAfYPdl2efB+1paFlI8BQDCNkruERKuuhG8LkQClDIVqUJLkrmKuOSPQ3xZHqVnVo3Q==, + } + engines: { node: ">=14.18.0" } + + readable-stream@4.7.0: + resolution: + { + integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + readdirp@4.1.2: + resolution: + { + integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==, + } + engines: { node: ">= 14.18.0" } + + real-require@0.2.0: + resolution: + { + integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==, + } + engines: { node: ">= 12.13.0" } + + regenerator-runtime@0.14.1: + resolution: + { + integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==, + } + + remeda@2.20.2: + resolution: + { + integrity: sha512-38pfm5aUq6mUkNYbt7TdY2WEk9mSqRVV+6UsoTjabwmbu8obLbh8sYYSX2WQ3W4u6EYp3XxUKqIiwGFZu+OY9g==, + } + + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, + } + engines: { node: ">=0.10.0" } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, + } + engines: { node: ">=0.10.0" } + + require-in-the-middle@7.5.1: + resolution: + { + integrity: sha512-fgZEz/t3FDrU9o7EhI+iNNq1pNNpJImOvX72HUd6RoFiw8MaKd8/gR5tLuc8A0G0e55LMbP6ImjnmXY6zrTmjw==, + } + engines: { node: ">=8.6.0" } + + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: ">=4" } + + resolve-pkg-maps@1.0.0: + resolution: + { + integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, + } + + resolve@1.22.10: + resolution: + { + integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==, + } + engines: { node: ">= 0.4" } + hasBin: true + + restore-cursor@5.1.0: + resolution: + { + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==, + } + engines: { node: ">=18" } + + reusify@1.0.4: + resolution: + { + integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==, + } + engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + + rfdc@1.4.1: + resolution: + { + integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, + } + + rimraf@5.0.10: + resolution: + { + integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==, + } + hasBin: true + + rollup@4.34.8: + resolution: + { + integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==, + } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } + hasBin: true + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + + safe-buffer@5.2.1: + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, + } + + safe-stable-stringify@2.5.0: + resolution: + { + integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==, + } + engines: { node: ">=10" } + + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, + } + + sax@1.4.1: + resolution: + { + integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==, + } + + secure-json-parse@2.7.0: + resolution: + { + integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==, + } + + seedrandom@3.0.5: + resolution: + { + integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==, + } + + semver@7.7.1: + resolution: + { + integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==, + } + engines: { node: ">=10" } + hasBin: true + + serialize-error@11.0.3: + resolution: + { + integrity: sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==, + } + engines: { node: ">=14.16" } + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: ">=8" } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: ">=8" } + + shimmer@1.2.1: + resolution: + { + integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==, + } + + siginfo@2.0.0: + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, + } + + signal-exit@4.1.0: + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, + } + engines: { node: ">=14" } + + slice-ansi@5.0.0: + resolution: + { + integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==, + } + engines: { node: ">=12" } + + slice-ansi@7.1.0: + resolution: + { + integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==, + } + engines: { node: ">=18" } + + sonic-boom@4.2.0: + resolution: + { + integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==, + } + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: ">=0.10.0" } + + split2@4.2.0: + resolution: + { + integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==, + } + engines: { node: ">= 10.x" } + + stackback@0.0.2: + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, + } + + std-env@3.8.0: + resolution: + { + integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==, + } + + string-argv@0.3.2: + resolution: + { + integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==, + } + engines: { node: ">=0.6.19" } + + string-collapse-leading-whitespace@7.0.7: + resolution: + { + integrity: sha512-jF9eynJoE6ezTCdYI8Qb02/ij/DlU9ItG93Dty4SWfJeLFrotOr+wH9IRiWHTqO3mjCyqBWEiU3uSTIbxYbAEQ==, + } + engines: { node: ">=14.18.0" } + + string-comparison@1.3.0: + resolution: + { + integrity: sha512-46aD+slEwybxAMPRII83ATbgMgTiz5P8mVd7Z6VJsCzSHFjdt1hkAVLeFxPIyEb11tc6ihpJTlIqoO0MCF6NPw==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + + string-left-right@6.0.17: + resolution: + { + integrity: sha512-nuyIV4D4ivnwT64E0TudmCRg52NfkumuEUilyoOrHb/Z2wEOF5I+9SI6P+veFKqWKZfGpAs6OqKe4nAjujARyw==, + } + engines: { node: ">=14.18.0" } + + string-strip-html@13.4.8: + resolution: + { + integrity: sha512-vlcRAtx5DN6zXGUx3EYGFg0/JOQWM65mqLgDaBHviQPP+ovUFzqZ30iQ+674JHWr9wNgnzFGxx9TGipPZMnZXg==, + } + engines: { node: ">=14.18.0" } + + string-trim-spaces-only@5.0.10: + resolution: + { + integrity: sha512-MhmjE5jNqb1Ylo+BARPRlsdChGLrnPpAUWrT1VOxo9WhWwKVUU6CbZTfjwKaQPYTGS/wsX/4Zek88FM2rEb5iA==, + } + engines: { node: ">=14.18.0" } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: ">=8" } + + string-width@5.1.2: + resolution: + { + integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, + } + engines: { node: ">=12" } + + string-width@7.2.0: + resolution: + { + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, + } + engines: { node: ">=18" } + + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, + } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: ">=8" } + + strip-ansi@7.1.0: + resolution: + { + integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==, + } + engines: { node: ">=12" } + + strip-final-newline@3.0.0: + resolution: + { + integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==, + } + engines: { node: ">=12" } + + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, + } + engines: { node: ">=8" } + + strnum@1.0.5: + resolution: + { + integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==, + } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: ">=8" } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, + } + engines: { node: ">= 0.4" } + + thread-stream@3.1.0: + resolution: + { + integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==, + } + + tiny-emitter@2.1.0: + resolution: + { + integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==, + } + + tiny-invariant@1.3.3: + resolution: + { + integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==, + } + + tinybench@2.9.0: + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, + } + + tinyexec@0.3.2: + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, + } + + tinyglobby@0.2.11: + resolution: + { + integrity: sha512-32TmKeeKUahv0Go8WmQgiEp9Y21NuxjwjqiRC1nrUB51YacfSwuB44xgXD+HdIppmMRgjQNPdrHyA6vIybYZ+g==, + } + engines: { node: ">=12.0.0" } + + tinypool@1.0.2: + resolution: + { + integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + + tinyrainbow@2.0.0: + resolution: + { + integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, + } + engines: { node: ">=14.0.0" } + + tinyspy@3.0.2: + resolution: + { + integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==, + } + engines: { node: ">=14.0.0" } + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: ">=8.0" } + + tr46@0.0.3: + resolution: + { + integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, + } + + ts-api-utils@2.0.1: + resolution: + { + integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==, + } + engines: { node: ">=18.12" } + peerDependencies: + typescript: ">=4.8.4" + + tsconfck@3.1.5: + resolution: + { + integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==, + } + engines: { node: ^18 || >=20 } + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + tsx@4.19.3: + resolution: + { + integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==, + } + engines: { node: ">=18.0.0" } + hasBin: true + + turndown@7.2.0: + resolution: + { + integrity: sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==, + } + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: ">= 0.8.0" } + + type-fest@2.19.0: + resolution: + { + integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==, + } + engines: { node: ">=12.20" } + + type-fest@4.35.0: + resolution: + { + integrity: sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==, + } + engines: { node: ">=16" } + + typed-function@4.2.1: + resolution: + { + integrity: sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==, + } + engines: { node: ">= 18" } + + typescript-eslint@8.24.1: + resolution: + { + integrity: sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + + typescript@5.5.4: + resolution: + { + integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==, + } + engines: { node: ">=14.17" } + hasBin: true + + undici-types@5.26.5: + resolution: + { + integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==, + } + + update-browserslist-db@1.1.2: + resolution: + { + integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==, + } + hasBin: true + peerDependencies: + browserslist: ">= 4.21.0" + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } + + uuid@9.0.1: + resolution: + { + integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==, + } + hasBin: true + + vali-date@1.0.0: + resolution: + { + integrity: sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==, + } + engines: { node: ">=0.10.0" } + + vite-node@3.0.6: + resolution: + { + integrity: sha512-s51RzrTkXKJrhNbUzQRsarjmAae7VmMPAsRT7lppVpIg6mK3zGthP9Hgz0YQQKuNcF+Ii7DfYk3Fxz40jRmePw==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + + vite-tsconfig-paths@5.1.4: + resolution: + { + integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==, + } + peerDependencies: + vite: "*" + peerDependenciesMeta: + vite: + optional: true + + vite@6.1.1: + resolution: + { + integrity: sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.0.6: + resolution: + { + integrity: sha512-/iL1Sc5VeDZKPDe58oGK4HUFLhw6b5XdY1MYawjuSaDA4sEfYlY9HnS6aCEG26fX+MgUi7MwlduTBHHAI/OvMA==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.0.6 + "@vitest/ui": 3.0.6 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + web-streams-polyfill@4.0.0-beta.3: + resolution: + { + integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==, + } + engines: { node: ">= 14" } + + webidl-conversions@3.0.1: + resolution: + { + integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, + } + + whatwg-fetch@3.6.20: + resolution: + { + integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==, + } + + whatwg-url@5.0.0: + resolution: + { + integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, + } + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: ">= 8" } + hasBin: true + + why-is-node-running@2.3.0: + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, + } + engines: { node: ">=8" } + hasBin: true + + wikipedia@2.1.2: + resolution: + { + integrity: sha512-RAYaMpXC9/E873RaSEtlEa8dXK4e0p5k98GKOd210MtkE5emm6fcnwD+N6ZA4cuffjDWagvhaQKtp/mGp2BOVQ==, + } + engines: { node: ">=10" } + + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: ">=0.10.0" } + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, + } + engines: { node: ">=10" } + + wrap-ansi@8.1.0: + resolution: + { + integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, + } + engines: { node: ">=12" } + + wrap-ansi@9.0.0: + resolution: + { + integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==, + } + engines: { node: ">=18" } + + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, + } + + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, + } + engines: { node: ">=10" } + + yaml@2.7.0: + resolution: + { + integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==, + } + engines: { node: ">= 14" } + hasBin: true + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: ">=12" } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, + } + engines: { node: ">=12" } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: ">=10" } + + zod-to-json-schema@3.24.1: + resolution: + { + integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==, + } + peerDependencies: + zod: ^3.24.1 + + zod@3.24.2: + resolution: + { + integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==, + } + +snapshots: + "@ai-zen/node-fetch-event-source@2.1.4": + dependencies: + cross-fetch: 4.1.0 + transitivePeerDependencies: + - encoding + + "@apidevtools/json-schema-ref-parser@11.9.1": + dependencies: + "@jsdevtools/ono": 7.1.3 + "@types/json-schema": 7.0.15 + js-yaml: 4.1.0 + + "@babel/runtime@7.26.9": + dependencies: + regenerator-runtime: 0.14.1 + + "@esbuild/aix-ppc64@0.24.2": + optional: true + + "@esbuild/aix-ppc64@0.25.0": + optional: true + + "@esbuild/android-arm64@0.24.2": + optional: true + + "@esbuild/android-arm64@0.25.0": + optional: true + + "@esbuild/android-arm@0.24.2": + optional: true + + "@esbuild/android-arm@0.25.0": + optional: true + + "@esbuild/android-x64@0.24.2": + optional: true + + "@esbuild/android-x64@0.25.0": + optional: true + + "@esbuild/darwin-arm64@0.24.2": + optional: true + + "@esbuild/darwin-arm64@0.25.0": + optional: true + + "@esbuild/darwin-x64@0.24.2": + optional: true + + "@esbuild/darwin-x64@0.25.0": + optional: true + + "@esbuild/freebsd-arm64@0.24.2": + optional: true + + "@esbuild/freebsd-arm64@0.25.0": + optional: true + + "@esbuild/freebsd-x64@0.24.2": + optional: true + + "@esbuild/freebsd-x64@0.25.0": + optional: true + + "@esbuild/linux-arm64@0.24.2": + optional: true + + "@esbuild/linux-arm64@0.25.0": + optional: true + + "@esbuild/linux-arm@0.24.2": + optional: true + + "@esbuild/linux-arm@0.25.0": + optional: true + + "@esbuild/linux-ia32@0.24.2": + optional: true + + "@esbuild/linux-ia32@0.25.0": + optional: true + + "@esbuild/linux-loong64@0.24.2": + optional: true + + "@esbuild/linux-loong64@0.25.0": + optional: true + + "@esbuild/linux-mips64el@0.24.2": + optional: true + + "@esbuild/linux-mips64el@0.25.0": + optional: true + + "@esbuild/linux-ppc64@0.24.2": + optional: true + + "@esbuild/linux-ppc64@0.25.0": + optional: true + + "@esbuild/linux-riscv64@0.24.2": + optional: true + + "@esbuild/linux-riscv64@0.25.0": + optional: true + + "@esbuild/linux-s390x@0.24.2": + optional: true + + "@esbuild/linux-s390x@0.25.0": + optional: true + + "@esbuild/linux-x64@0.24.2": + optional: true + + "@esbuild/linux-x64@0.25.0": + optional: true + + "@esbuild/netbsd-arm64@0.24.2": + optional: true + + "@esbuild/netbsd-arm64@0.25.0": + optional: true + + "@esbuild/netbsd-x64@0.24.2": + optional: true + + "@esbuild/netbsd-x64@0.25.0": + optional: true + + "@esbuild/openbsd-arm64@0.24.2": + optional: true + + "@esbuild/openbsd-arm64@0.25.0": + optional: true + + "@esbuild/openbsd-x64@0.24.2": + optional: true + + "@esbuild/openbsd-x64@0.25.0": + optional: true + + "@esbuild/sunos-x64@0.24.2": + optional: true + + "@esbuild/sunos-x64@0.25.0": + optional: true + + "@esbuild/win32-arm64@0.24.2": + optional: true + + "@esbuild/win32-arm64@0.25.0": + optional: true + + "@esbuild/win32-ia32@0.24.2": + optional: true + + "@esbuild/win32-ia32@0.25.0": + optional: true + + "@esbuild/win32-x64@0.24.2": + optional: true + + "@esbuild/win32-x64@0.25.0": + optional: true + + "@eslint-community/eslint-utils@4.4.1(eslint@9.20.1)": + dependencies: + eslint: 9.20.1 + eslint-visitor-keys: 3.4.3 + + "@eslint-community/regexpp@4.12.1": {} + + "@eslint/config-array@0.19.2": + dependencies: + "@eslint/object-schema": 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + "@eslint/core@0.11.0": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/eslintrc@3.2.0": + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + "@eslint/js@9.20.0": {} + + "@eslint/object-schema@2.1.6": {} + + "@eslint/plugin-kit@0.2.6": + dependencies: + "@eslint/core": 0.11.0 + levn: 0.4.1 + + "@google-cloud/vertexai@1.9.3": + dependencies: + google-auth-library: 9.15.1 + transitivePeerDependencies: + - encoding + - supports-color + + "@grpc/grpc-js@1.12.6": + dependencies: + "@grpc/proto-loader": 0.7.13 + "@js-sdsl/ordered-map": 4.4.2 + + "@grpc/proto-loader@0.7.13": + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.1 + protobufjs: 7.4.0 + yargs: 17.7.2 + + "@humanfs/core@0.19.1": {} + + "@humanfs/node@0.16.6": + dependencies: + "@humanfs/core": 0.19.1 + "@humanwhocodes/retry": 0.3.1 + + "@humanwhocodes/module-importer@1.0.1": {} + + "@humanwhocodes/retry@0.3.1": {} + + "@humanwhocodes/retry@0.4.2": {} + + "@ibm-generative-ai/node-sdk@3.2.4": + dependencies: + "@ai-zen/node-fetch-event-source": 2.1.4 + fetch-retry: 5.0.6 + http-status-codes: 2.3.0 + openapi-fetch: 0.8.2 + p-queue-compat: 1.0.225 + yaml: 2.7.0 + transitivePeerDependencies: + - encoding + + "@isaacs/cliui@8.0.2": + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + "@jridgewell/sourcemap-codec@1.5.0": {} + + "@js-sdsl/ordered-map@4.4.2": {} + + "@jsdevtools/ono@7.1.3": {} + + "@mixmark-io/domino@2.2.0": {} + + "@nodelib/fs.scandir@2.1.5": + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: 1.2.0 + + "@nodelib/fs.stat@2.0.5": {} + + "@nodelib/fs.walk@1.2.8": + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: 1.19.0 + + "@opentelemetry/api-logs@0.57.2": + dependencies: + "@opentelemetry/api": 1.9.0 + + "@opentelemetry/api@1.9.0": {} + + "@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + + "@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/semantic-conventions": 1.28.0 + + "@opentelemetry/exporter-logs-otlp-grpc@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@grpc/grpc-js": 1.12.6 + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-grpc-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-logs": 0.57.2(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-logs-otlp-http@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.57.2 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-logs": 0.57.2(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-logs-otlp-proto@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.57.2 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-logs": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-metrics-otlp-grpc@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@grpc/grpc-js": 1.12.6 + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-metrics-otlp-http": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-grpc-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-metrics": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-metrics-otlp-http@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-metrics": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-metrics-otlp-proto@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-metrics-otlp-http": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-metrics": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-prometheus@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-metrics": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-trace-otlp-grpc@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@grpc/grpc-js": 1.12.6 + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-grpc-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-trace-otlp-http@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-trace-otlp-proto@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/exporter-zipkin@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.28.0 + + "@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.57.2 + "@types/shimmer": 1.2.0 + import-in-the-middle: 1.13.0 + require-in-the-middle: 7.5.1 + semver: 7.7.1 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/otlp-exporter-base@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + + "@opentelemetry/otlp-grpc-exporter-base@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@grpc/grpc-js": 1.12.6 + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-exporter-base": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/otlp-transformer": 0.57.2(@opentelemetry/api@1.9.0) + + "@opentelemetry/otlp-transformer@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.57.2 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-logs": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-metrics": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 1.30.1(@opentelemetry/api@1.9.0) + protobufjs: 7.4.0 + + "@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.28.0 + + "@opentelemetry/sdk-logs@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.57.2 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + + "@opentelemetry/sdk-node@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.57.2 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-logs-otlp-grpc": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-logs-otlp-http": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-logs-otlp-proto": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-metrics-otlp-grpc": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-metrics-otlp-http": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-metrics-otlp-proto": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-prometheus": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-trace-otlp-grpc": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-trace-otlp-http": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-trace-otlp-proto": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/exporter-zipkin": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-logs": 0.57.2(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-metrics": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-node": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.28.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.28.0 + + "@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/context-async-hooks": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/core": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/propagator-b3": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/propagator-jaeger": 1.30.1(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 1.30.1(@opentelemetry/api@1.9.0) + semver: 7.7.1 + + "@opentelemetry/semantic-conventions@1.28.0": {} + + "@pkgjs/parseargs@0.11.0": + optional: true + + "@protobufjs/aspromise@1.1.2": {} + + "@protobufjs/base64@1.1.2": {} + + "@protobufjs/codegen@2.0.4": {} + + "@protobufjs/eventemitter@1.1.0": {} + + "@protobufjs/fetch@1.1.0": + dependencies: + "@protobufjs/aspromise": 1.1.2 + "@protobufjs/inquire": 1.1.0 + + "@protobufjs/float@1.0.2": {} + + "@protobufjs/inquire@1.1.0": {} + + "@protobufjs/path@1.1.2": {} + + "@protobufjs/pool@1.1.0": {} + + "@protobufjs/utf8@1.1.0": {} + + "@rollup/rollup-android-arm-eabi@4.34.8": + optional: true + + "@rollup/rollup-android-arm64@4.34.8": + optional: true + + "@rollup/rollup-darwin-arm64@4.34.8": + optional: true + + "@rollup/rollup-darwin-x64@4.34.8": + optional: true + + "@rollup/rollup-freebsd-arm64@4.34.8": + optional: true + + "@rollup/rollup-freebsd-x64@4.34.8": + optional: true + + "@rollup/rollup-linux-arm-gnueabihf@4.34.8": + optional: true + + "@rollup/rollup-linux-arm-musleabihf@4.34.8": + optional: true + + "@rollup/rollup-linux-arm64-gnu@4.34.8": + optional: true + + "@rollup/rollup-linux-arm64-musl@4.34.8": + optional: true + + "@rollup/rollup-linux-loongarch64-gnu@4.34.8": + optional: true + + "@rollup/rollup-linux-powerpc64le-gnu@4.34.8": + optional: true + + "@rollup/rollup-linux-riscv64-gnu@4.34.8": + optional: true + + "@rollup/rollup-linux-s390x-gnu@4.34.8": + optional: true + + "@rollup/rollup-linux-x64-gnu@4.34.8": + optional: true + + "@rollup/rollup-linux-x64-musl@4.34.8": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.34.8": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.34.8": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.34.8": + optional: true + + "@sindresorhus/is@4.6.0": {} + + "@streamparser/json@0.0.21": {} + + "@types/blessed@0.1.25": + dependencies: + "@types/node": 18.19.76 + + "@types/eslint-config-prettier@6.11.3": {} + + "@types/eslint@9.6.1": + dependencies: + "@types/estree": 1.0.6 + "@types/json-schema": 7.0.15 + + "@types/eslint__js@8.42.3": + dependencies: + "@types/eslint": 9.6.1 + + "@types/estree@1.0.6": {} + + "@types/json-schema@7.0.15": {} + + "@types/lodash-es@4.17.12": + dependencies: + "@types/lodash": 4.17.15 + + "@types/lodash@4.17.15": {} + + "@types/node-fetch@2.6.12": + dependencies: + "@types/node": 18.19.76 + form-data: 4.0.2 + + "@types/node@18.19.76": + dependencies: + undici-types: 5.26.5 + + "@types/shimmer@1.2.0": {} + + "@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1)(typescript@5.5.4))(eslint@9.20.1)(typescript@5.5.4)": + dependencies: + "@eslint-community/regexpp": 4.12.1 + "@typescript-eslint/parser": 8.24.1(eslint@9.20.1)(typescript@5.5.4) + "@typescript-eslint/scope-manager": 8.24.1 + "@typescript-eslint/type-utils": 8.24.1(eslint@9.20.1)(typescript@5.5.4) + "@typescript-eslint/utils": 8.24.1(eslint@9.20.1)(typescript@5.5.4) + "@typescript-eslint/visitor-keys": 8.24.1 + eslint: 9.20.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.1(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/parser@8.24.1(eslint@9.20.1)(typescript@5.5.4)": + dependencies: + "@typescript-eslint/scope-manager": 8.24.1 + "@typescript-eslint/types": 8.24.1 + "@typescript-eslint/typescript-estree": 8.24.1(typescript@5.5.4) + "@typescript-eslint/visitor-keys": 8.24.1 + debug: 4.4.0 + eslint: 9.20.1 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/scope-manager@8.24.1": + dependencies: + "@typescript-eslint/types": 8.24.1 + "@typescript-eslint/visitor-keys": 8.24.1 + + "@typescript-eslint/type-utils@8.24.1(eslint@9.20.1)(typescript@5.5.4)": + dependencies: + "@typescript-eslint/typescript-estree": 8.24.1(typescript@5.5.4) + "@typescript-eslint/utils": 8.24.1(eslint@9.20.1)(typescript@5.5.4) + debug: 4.4.0 + eslint: 9.20.1 + ts-api-utils: 2.0.1(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/types@8.24.1": {} + + "@typescript-eslint/typescript-estree@8.24.1(typescript@5.5.4)": + dependencies: + "@typescript-eslint/types": 8.24.1 + "@typescript-eslint/visitor-keys": 8.24.1 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.0.1(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/utils@8.24.1(eslint@9.20.1)(typescript@5.5.4)": + dependencies: + "@eslint-community/eslint-utils": 4.4.1(eslint@9.20.1) + "@typescript-eslint/scope-manager": 8.24.1 + "@typescript-eslint/types": 8.24.1 + "@typescript-eslint/typescript-estree": 8.24.1(typescript@5.5.4) + eslint: 9.20.1 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/visitor-keys@8.24.1": + dependencies: + "@typescript-eslint/types": 8.24.1 + eslint-visitor-keys: 4.2.0 + + "@vitest/expect@3.0.6": + dependencies: + "@vitest/spy": 3.0.6 + "@vitest/utils": 3.0.6 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + "@vitest/mocker@3.0.6(vite@6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0))": + dependencies: + "@vitest/spy": 3.0.6 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0) + + "@vitest/pretty-format@3.0.6": + dependencies: + tinyrainbow: 2.0.0 + + "@vitest/runner@3.0.6": + dependencies: + "@vitest/utils": 3.0.6 + pathe: 2.0.3 + + "@vitest/snapshot@3.0.6": + dependencies: + "@vitest/pretty-format": 3.0.6 + magic-string: 0.30.17 + pathe: 2.0.3 + + "@vitest/spy@3.0.6": + dependencies: + tinyspy: 3.0.2 + + "@vitest/utils@3.0.6": + dependencies: + "@vitest/pretty-format": 3.0.6 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-import-attributes@1.9.5(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + adm-zip@0.5.16: {} + + agent-base@7.1.3: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.6 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + asynckit@0.4.0: {} + + atomic-sleep@1.0.0: {} + + axios@1.7.9: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0): + dependencies: + "@ai-zen/node-fetch-event-source": 2.1.4 + "@opentelemetry/api": 1.9.0 + "@streamparser/json": 0.0.21 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + duck-duck-scrape: 2.2.7 + fast-xml-parser: 4.5.2 + header-generator: 2.1.62 + joplin-turndown-plugin-gfm: 1.0.12 + js-yaml: 4.1.0 + json-schema-to-typescript: 15.0.4 + jsonrepair: 3.12.0 + mathjs: 14.2.1 + mustache: 4.2.0 + object-hash: 3.0.0 + p-queue-compat: 1.0.229 + p-throttle: 7.0.0 + pino: 9.6.0 + promise-based-task: 3.1.1 + remeda: 2.20.2 + serialize-error: 11.0.3 + string-comparison: 1.3.0 + string-strip-html: 13.4.8 + turndown: 7.2.0 + wikipedia: 2.1.2 + zod: 3.24.2 + zod-to-json-schema: 3.24.1(zod@3.24.2) + optionalDependencies: + "@google-cloud/vertexai": 1.9.3 + "@grpc/grpc-js": 1.12.6 + "@grpc/proto-loader": 0.7.13 + google-auth-library: 9.15.1 + groq-sdk: 0.7.0 + ollama: 0.5.13 + openai: 4.85.2(zod@3.24.2) + openai-chat-tokens: 0.2.8 + yaml: 2.7.0 + transitivePeerDependencies: + - debug + - encoding + + bee-observe-connector@0.0.6(bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0)): + dependencies: + bee-agent-framework: 0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0) + openapi-fetch: 0.11.3 + remeda: 2.20.2 + + bignumber.js@9.1.2: {} + + blessed@0.1.81: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001700 + electron-to-chromium: 1.5.102 + node-releases: 2.0.19 + update-browserslist-db: 1.1.2(browserslist@4.24.4) + + buffer-equal-constant-time@1.0.1: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + camelcase@4.1.0: {} + + caniuse-lite@1.0.30001700: {} + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.4.1: {} + + check-error@2.1.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + cjs-module-lexer@1.4.3: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + codsen-utils@1.6.4: + dependencies: + rfdc: 1.4.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@13.1.0: {} + + complex.js@2.4.2: {} + + concat-map@0.0.1: {} + + cross-fetch@4.1.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + dateformat@4.6.3: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + decimal.js@10.5.0: {} + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + dot-prop@6.0.1: + dependencies: + is-obj: 2.0.0 + + dotenv@16.4.7: {} + + duck-duck-scrape@2.2.7: + dependencies: + html-entities: 2.5.2 + needle: 3.3.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + electron-to-chromium@1.5.102: {} + + emoji-regex@10.4.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + environment@1.1.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.6.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.24.2: + optionalDependencies: + "@esbuild/aix-ppc64": 0.24.2 + "@esbuild/android-arm": 0.24.2 + "@esbuild/android-arm64": 0.24.2 + "@esbuild/android-x64": 0.24.2 + "@esbuild/darwin-arm64": 0.24.2 + "@esbuild/darwin-x64": 0.24.2 + "@esbuild/freebsd-arm64": 0.24.2 + "@esbuild/freebsd-x64": 0.24.2 + "@esbuild/linux-arm": 0.24.2 + "@esbuild/linux-arm64": 0.24.2 + "@esbuild/linux-ia32": 0.24.2 + "@esbuild/linux-loong64": 0.24.2 + "@esbuild/linux-mips64el": 0.24.2 + "@esbuild/linux-ppc64": 0.24.2 + "@esbuild/linux-riscv64": 0.24.2 + "@esbuild/linux-s390x": 0.24.2 + "@esbuild/linux-x64": 0.24.2 + "@esbuild/netbsd-arm64": 0.24.2 + "@esbuild/netbsd-x64": 0.24.2 + "@esbuild/openbsd-arm64": 0.24.2 + "@esbuild/openbsd-x64": 0.24.2 + "@esbuild/sunos-x64": 0.24.2 + "@esbuild/win32-arm64": 0.24.2 + "@esbuild/win32-ia32": 0.24.2 + "@esbuild/win32-x64": 0.24.2 + + esbuild@0.25.0: + optionalDependencies: + "@esbuild/aix-ppc64": 0.25.0 + "@esbuild/android-arm": 0.25.0 + "@esbuild/android-arm64": 0.25.0 + "@esbuild/android-x64": 0.25.0 + "@esbuild/darwin-arm64": 0.25.0 + "@esbuild/darwin-x64": 0.25.0 + "@esbuild/freebsd-arm64": 0.25.0 + "@esbuild/freebsd-x64": 0.25.0 + "@esbuild/linux-arm": 0.25.0 + "@esbuild/linux-arm64": 0.25.0 + "@esbuild/linux-ia32": 0.25.0 + "@esbuild/linux-loong64": 0.25.0 + "@esbuild/linux-mips64el": 0.25.0 + "@esbuild/linux-ppc64": 0.25.0 + "@esbuild/linux-riscv64": 0.25.0 + "@esbuild/linux-s390x": 0.25.0 + "@esbuild/linux-x64": 0.25.0 + "@esbuild/netbsd-arm64": 0.25.0 + "@esbuild/netbsd-x64": 0.25.0 + "@esbuild/openbsd-arm64": 0.25.0 + "@esbuild/openbsd-x64": 0.25.0 + "@esbuild/sunos-x64": 0.25.0 + "@esbuild/win32-arm64": 0.25.0 + "@esbuild/win32-ia32": 0.25.0 + "@esbuild/win32-x64": 0.25.0 + + escalade@3.2.0: {} + + escape-latex@1.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.0(eslint@9.20.1): + dependencies: + eslint: 9.20.1 + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.20.1: + dependencies: + "@eslint-community/eslint-utils": 4.4.1(eslint@9.20.1) + "@eslint-community/regexpp": 4.12.1 + "@eslint/config-array": 0.19.2 + "@eslint/core": 0.11.0 + "@eslint/eslintrc": 3.2.0 + "@eslint/js": 9.20.0 + "@eslint/plugin-kit": 0.2.6 + "@humanfs/node": 0.16.6 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.2 + "@types/estree": 1.0.6 + "@types/json-schema": 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + "@types/estree": 1.0.6 + + esutils@2.0.3: {} + + event-target-shim@5.0.1: {} + + eventemitter3@5.0.1: {} + + events@3.3.0: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + expect-type@1.1.0: {} + + extend@3.0.2: {} + + fast-copy@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-redact@3.5.0: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.0.6: {} + + fast-xml-parser@4.5.2: + dependencies: + strnum: 1.0.5 + + fastq@1.19.0: + dependencies: + reusify: 1.0.4 + + fdir@6.4.3(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fetch-retry@5.0.6: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.9: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data-encoder@1.7.2: {} + + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + fraction.js@5.2.1: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gaxios@6.7.1: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + is-stream: 2.0.1 + node-fetch: 2.7.0 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + gcp-metadata@6.1.1: + dependencies: + gaxios: 6.7.1 + google-logging-utils: 0.0.2 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + generative-bayesian-network@2.1.62: + dependencies: + adm-zip: 0.5.16 + tslib: 2.8.1 + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.3.0: {} + + get-intrinsic@1.2.7: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@8.0.1: {} + + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@14.0.0: {} + + globrex@0.1.2: {} + + google-auth-library@9.15.1: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.7.1 + gcp-metadata: 6.1.1 + gtoken: 7.1.0 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + google-logging-utils@0.0.2: {} + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + groq-sdk@0.7.0: + dependencies: + "@types/node": 18.19.76 + "@types/node-fetch": 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + gtoken@7.1.0: + dependencies: + gaxios: 6.7.1 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + header-generator@2.1.62: + dependencies: + browserslist: 4.24.4 + generative-bayesian-network: 2.1.62 + ow: 0.28.2 + tslib: 2.8.1 + + help-me@5.0.0: {} + + html-entities@2.5.2: {} + + http-status-codes@2.3.0: {} + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + human-signals@5.0.0: {} + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + husky@9.1.7: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-in-the-middle@1.13.0: + dependencies: + acorn: 8.14.0 + acorn-import-attributes: 1.9.5(acorn@8.14.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.3 + + imurmurhash@0.1.4: {} + + infobox-parser@3.6.4: + dependencies: + camelcase: 4.1.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.3.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + "@isaacs/cliui": 8.0.2 + optionalDependencies: + "@pkgjs/parseargs": 0.11.0 + + javascript-natural-sort@0.7.1: {} + + joplin-turndown-plugin-gfm@1.0.12: {} + + joycon@3.1.1: {} + + js-tiktoken@1.0.19: + dependencies: + base64-js: 1.5.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.1.2 + + json-buffer@3.0.1: {} + + json-schema-to-typescript@15.0.4: + dependencies: + "@apidevtools/json-schema-ref-parser": 11.9.1 + "@types/json-schema": 7.0.15 + "@types/lodash": 4.17.15 + is-glob: 4.0.3 + js-yaml: 4.1.0 + lodash: 4.17.21 + minimist: 1.2.8 + prettier: 3.5.1 + tinyglobby: 0.2.11 + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonrepair@3.12.0: {} + + jwa@2.0.0: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.0: + dependencies: + jwa: 2.0.0 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lint-staged@15.4.3: + dependencies: + chalk: 5.4.1 + commander: 13.1.0 + debug: 4.4.0 + execa: 8.0.1 + lilconfig: 3.1.3 + listr2: 8.2.5 + micromatch: 4.0.8 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.7.0 + transitivePeerDependencies: + - supports-color + + listr2@8.2.5: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash.camelcase@4.3.0: {} + + lodash.isequal@4.5.0: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + long@5.3.1: {} + + loupe@3.1.3: {} + + lru-cache@10.4.3: {} + + magic-string@0.30.17: + dependencies: + "@jridgewell/sourcemap-codec": 1.5.0 + + math-intrinsics@1.1.0: {} + + mathjs@14.2.1: + dependencies: + "@babel/runtime": 7.26.9 + complex.js: 2.4.2 + decimal.js: 10.5.0 + escape-latex: 1.2.0 + fraction.js: 5.2.1 + javascript-natural-sort: 0.7.1 + seedrandom: 3.0.5 + tiny-emitter: 2.1.0 + typed-function: 4.2.1 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + module-details-from-path@1.0.3: {} + + ms@2.1.3: {} + + mustache@4.2.0: {} + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + needle@3.3.1: + dependencies: + iconv-lite: 0.6.3 + sax: 1.4.1 + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.19: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + object-hash@3.0.0: {} + + ollama@0.5.13: + dependencies: + whatwg-fetch: 3.6.20 + + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + openai-chat-tokens@0.2.8: + dependencies: + js-tiktoken: 1.0.19 + + openai@4.85.2(zod@3.24.2): + dependencies: + "@types/node": 18.19.76 + "@types/node-fetch": 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + zod: 3.24.2 + transitivePeerDependencies: + - encoding + + openapi-fetch@0.11.3: + dependencies: + openapi-typescript-helpers: 0.0.13 + + openapi-fetch@0.8.2: + dependencies: + openapi-typescript-helpers: 0.0.5 + + openapi-typescript-helpers@0.0.13: {} + + openapi-typescript-helpers@0.0.5: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ow@0.28.2: + dependencies: + "@sindresorhus/is": 4.6.0 + callsites: 3.1.0 + dot-prop: 6.0.1 + lodash.isequal: 4.5.0 + vali-date: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-queue-compat@1.0.225: + dependencies: + eventemitter3: 5.0.1 + p-timeout-compat: 1.0.6 + + p-queue-compat@1.0.229: + dependencies: + eventemitter3: 5.0.1 + p-timeout-compat: 1.0.6 + + p-throttle@7.0.0: {} + + p-timeout-compat@1.0.6: {} + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + pathe@2.0.3: {} + + pathval@2.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pidtree@0.6.0: {} + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@11.3.0: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 3.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pump: 3.0.2 + readable-stream: 4.7.0 + secure-json-parse: 2.7.0 + sonic-boom: 4.2.0 + strip-json-comments: 3.1.1 + + pino-std-serializers@7.0.0: {} + + pino@9.6.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 4.0.1 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.5.1: {} + + process-warning@4.0.1: {} + + process@0.11.10: {} + + promise-based-task@3.1.1: {} + + protobufjs@7.4.0: + dependencies: + "@protobufjs/aspromise": 1.1.2 + "@protobufjs/base64": 1.1.2 + "@protobufjs/codegen": 2.0.4 + "@protobufjs/eventemitter": 1.1.0 + "@protobufjs/fetch": 1.1.0 + "@protobufjs/float": 1.0.2 + "@protobufjs/inquire": 1.1.0 + "@protobufjs/path": 1.1.2 + "@protobufjs/pool": 1.1.0 + "@protobufjs/utf8": 1.1.0 + "@types/node": 18.19.76 + long: 5.3.1 + + proxy-from-env@1.1.0: {} + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + quick-format-unescaped@4.0.4: {} + + ranges-apply@7.0.16: + dependencies: + ranges-merge: 9.0.15 + tiny-invariant: 1.3.3 + + ranges-merge@9.0.15: + dependencies: + ranges-push: 7.0.15 + ranges-sort: 6.0.11 + + ranges-push@7.0.15: + dependencies: + codsen-utils: 1.6.4 + ranges-sort: 6.0.11 + string-collapse-leading-whitespace: 7.0.7 + string-trim-spaces-only: 5.0.10 + + ranges-sort@6.0.11: {} + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdirp@4.1.2: {} + + real-require@0.2.0: {} + + regenerator-runtime@0.14.1: {} + + remeda@2.20.2: + dependencies: + type-fest: 4.35.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-in-the-middle@7.5.1: + dependencies: + debug: 4.4.0 + module-details-from-path: 1.0.3 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.0.4: {} + + rfdc@1.4.1: {} + + rimraf@5.0.10: + dependencies: + glob: 10.4.5 + + rollup@4.34.8: + dependencies: + "@types/estree": 1.0.6 + optionalDependencies: + "@rollup/rollup-android-arm-eabi": 4.34.8 + "@rollup/rollup-android-arm64": 4.34.8 + "@rollup/rollup-darwin-arm64": 4.34.8 + "@rollup/rollup-darwin-x64": 4.34.8 + "@rollup/rollup-freebsd-arm64": 4.34.8 + "@rollup/rollup-freebsd-x64": 4.34.8 + "@rollup/rollup-linux-arm-gnueabihf": 4.34.8 + "@rollup/rollup-linux-arm-musleabihf": 4.34.8 + "@rollup/rollup-linux-arm64-gnu": 4.34.8 + "@rollup/rollup-linux-arm64-musl": 4.34.8 + "@rollup/rollup-linux-loongarch64-gnu": 4.34.8 + "@rollup/rollup-linux-powerpc64le-gnu": 4.34.8 + "@rollup/rollup-linux-riscv64-gnu": 4.34.8 + "@rollup/rollup-linux-s390x-gnu": 4.34.8 + "@rollup/rollup-linux-x64-gnu": 4.34.8 + "@rollup/rollup-linux-x64-musl": 4.34.8 + "@rollup/rollup-win32-arm64-msvc": 4.34.8 + "@rollup/rollup-win32-ia32-msvc": 4.34.8 + "@rollup/rollup-win32-x64-msvc": 4.34.8 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + sax@1.4.1: {} + + secure-json-parse@2.7.0: {} + + seedrandom@3.0.5: {} + + semver@7.7.1: {} + + serialize-error@11.0.3: + dependencies: + type-fest: 2.19.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shimmer@1.2.1: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.1: {} + + split2@4.2.0: {} + + stackback@0.0.2: {} + + std-env@3.8.0: {} + + string-argv@0.3.2: {} + + string-collapse-leading-whitespace@7.0.7: {} + + string-comparison@1.3.0: {} + + string-left-right@6.0.17: + dependencies: + codsen-utils: 1.6.4 + rfdc: 1.4.1 + + string-strip-html@13.4.8: + dependencies: + "@types/lodash-es": 4.17.12 + codsen-utils: 1.6.4 + html-entities: 2.5.2 + lodash-es: 4.17.21 + ranges-apply: 7.0.16 + ranges-push: 7.0.15 + string-left-right: 6.0.17 + + string-trim-spaces-only@5.0.10: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + strnum@1.0.5: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + tiny-emitter@2.1.0: {} + + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.11: + dependencies: + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.0.2: {} + + tinyrainbow@2.0.0: {} + + tinyspy@3.0.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + ts-api-utils@2.0.1(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + + tsconfck@3.1.5(typescript@5.5.4): + optionalDependencies: + typescript: 5.5.4 + + tslib@2.8.1: {} + + tsx@4.19.3: + dependencies: + esbuild: 0.25.0 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + + turndown@7.2.0: + dependencies: + "@mixmark-io/domino": 2.2.0 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@2.19.0: {} + + type-fest@4.35.0: {} + + typed-function@4.2.1: {} + + typescript-eslint@8.24.1(eslint@9.20.1)(typescript@5.5.4): + dependencies: + "@typescript-eslint/eslint-plugin": 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1)(typescript@5.5.4))(eslint@9.20.1)(typescript@5.5.4) + "@typescript-eslint/parser": 8.24.1(eslint@9.20.1)(typescript@5.5.4) + "@typescript-eslint/utils": 8.24.1(eslint@9.20.1)(typescript@5.5.4) + eslint: 9.20.1 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + typescript@5.5.4: {} + + undici-types@5.26.5: {} + + update-browserslist-db@1.1.2(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + uuid@9.0.1: {} + + vali-date@1.0.0: {} + + vite-node@3.0.6(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0): + dependencies: + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 2.0.3 + vite: 6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0) + transitivePeerDependencies: + - "@types/node" + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite-tsconfig-paths@5.1.4(typescript@5.5.4)(vite@6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0)): + dependencies: + debug: 4.4.0 + globrex: 0.1.2 + tsconfck: 3.1.5(typescript@5.5.4) + optionalDependencies: + vite: 6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0) + transitivePeerDependencies: + - supports-color + - typescript + + vite@6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0): + dependencies: + esbuild: 0.24.2 + postcss: 8.5.3 + rollup: 4.34.8 + optionalDependencies: + "@types/node": 18.19.76 + fsevents: 2.3.3 + tsx: 4.19.3 + yaml: 2.7.0 + + vitest@3.0.6(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0): + dependencies: + "@vitest/expect": 3.0.6 + "@vitest/mocker": 3.0.6(vite@6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0)) + "@vitest/pretty-format": 3.0.6 + "@vitest/runner": 3.0.6 + "@vitest/snapshot": 3.0.6 + "@vitest/spy": 3.0.6 + "@vitest/utils": 3.0.6 + chai: 5.2.0 + debug: 4.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.1.1(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0) + vite-node: 3.0.6(@types/node@18.19.76)(tsx@4.19.3)(yaml@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + "@types/node": 18.19.76 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-fetch@3.6.20: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wikipedia@2.1.2: + dependencies: + axios: 1.7.9 + infobox-parser: 3.6.4 + transitivePeerDependencies: + - debug + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + y18n@5.0.8: {} + + yaml@2.7.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zod-to-json-schema@3.24.1(zod@3.24.2): + dependencies: + zod: 3.24.2 + + zod@3.24.2: {} diff --git a/src/agents/index.ts b/src/agents/index.ts new file mode 100644 index 0000000..0c74d2a --- /dev/null +++ b/src/agents/index.ts @@ -0,0 +1,4 @@ +export * as agentsTool from "./tool.js"; +export * as agentsStateLogger from "./state/logger.js"; +export * as supervisor from "./supervisor.js"; +export * as operator from "./operator.js"; diff --git a/src/agents/registry/registry.ts b/src/agents/registry/registry.ts index b0d6b58..97187ca 100644 --- a/src/agents/registry/registry.ts +++ b/src/agents/registry/registry.ts @@ -1,8 +1,6 @@ import { clone, isNonNullish } from "remeda"; import { BaseToolsFactory } from "src/base/tools-factory.js"; import { updateDeepPartialObject } from "src/utils/objects.js"; -import { WorkspaceResource } from "src/workspace/workspace-manager.js"; -import { WorkspaceRestorable } from "src/workspace/workspace-restorable.js"; import { agentConfigIdToValue, agentIdToString, @@ -10,7 +8,6 @@ import { agentSomeIdToTypeValue, stringToAgentConfig, } from "../agent-id.js"; -import { agentStateLogger } from "../state/logger.js"; import { Agent, AgentConfig, @@ -24,6 +21,9 @@ import { AgentTypeValue, AgentWithInstance, } from "./dto.js"; +import { AgentStateLogger } from "@agents/state/logger.js"; +import { WorkspaceResource } from "@workspaces/workspace-manager.js"; +import { WorkspaceRestorable } from "@workspaces/workspace-restorable.js"; /** * Callbacks for managing agent lifecycle events. @@ -95,6 +95,7 @@ export class AgentRegistry extends WorkspaceRestorable { private poolsCleanupJobIntervalId: NodeJS.Timeout | null = null; private poolsCleanupJobExecuting = false; private poolsToCleanup: string[] = []; + private stateLogger: AgentStateLogger; /** * Creates a new AgentRegistry instance @@ -109,6 +110,7 @@ export class AgentRegistry extends WorkspaceRestorable { }) { super(AGENT_REGISTRY_CONFIG_PATH, AGENT_REGISTRY_USER); this.logger.info("Initializing AgentRegistry"); + this.stateLogger = AgentStateLogger.getInstance(); this.lifecycleCallbacks = agentLifecycle; this.onAgentConfigCreated = onAgentConfigCreated; // Initialize agent pools for all agent kinds @@ -165,7 +167,7 @@ export class AgentRegistry extends WorkspaceRestorable { registerToolsFactories(tuples: [AgentKindEnum, BaseToolsFactory][]) { tuples.map(([agentKind, factory]) => { this.toolsFactory.set(agentKind, factory); - agentStateLogger().logAvailableTools({ + this.stateLogger.logAvailableTools({ agentKindId: agentSomeIdToKindValue({ agentKind }), availableTools: factory.getAvailableTools(), }); @@ -279,7 +281,7 @@ export class AgentRegistry extends WorkspaceRestorable { }); const configVersioned = { ...config, agentConfigId, agentConfigVersion }; agentTypesMap.set(agentType, [configVersioned]); - agentStateLogger().logAgentConfigCreate({ + this.stateLogger.logAgentConfigCreate({ agentConfigId, agentType: agentSomeIdToTypeValue(configVersioned), config: configVersioned, @@ -343,7 +345,7 @@ export class AgentRegistry extends WorkspaceRestorable { this.initializeAgentPool(agentKind, agentType, agentConfigVersion); this.lookupPoolsToClean(); - agentStateLogger().logAgentConfigUpdate({ + this.stateLogger.logAgentConfigUpdate({ agentType: agentSomeIdToTypeValue(newConfigVersion), agentConfigId: newConfigVersion.agentConfigId, config: newConfigVersion, @@ -628,7 +630,7 @@ export class AgentRegistry extends WorkspaceRestorable { agentKind, agentType, }); - agentStateLogger().logAgentConfigDestroy({ + this.stateLogger.logAgentConfigDestroy({ agentConfigId, agentType: agentTypeId, }); @@ -702,12 +704,12 @@ export class AgentRegistry extends WorkspaceRestorable { this.logger.trace("Executing onAcquire callback", { agentId }); await this.lifecycleCallbacks.onAcquire(agentId); } - agentStateLogger().logAgentAcquire({ + this.stateLogger.logAgentAcquire({ agentId: agent.agentId, }); const [poolStats, versions] = this.getPoolStats(agent.agentKind, agent.agentType); - agentStateLogger().logPoolChange({ + this.stateLogger.logPoolChange({ agentTypeId: agentSomeIdToTypeValue(agent), poolStats, versions, @@ -747,12 +749,12 @@ export class AgentRegistry extends WorkspaceRestorable { agent.inUse = false; pool.add(agentId); this.logger.debug("Agent released back to pool", { agentId, agentType: agent.agentType }); - agentStateLogger().logAgentRelease({ + this.stateLogger.logAgentRelease({ agentId: agent.agentId, }); const [poolStats, versions] = this.getPoolStats(agent.agentKind, agent.agentType); - agentStateLogger().logPoolChange({ + this.stateLogger.logPoolChange({ agentTypeId: agentSomeIdToTypeValue(agent), poolStats, versions, @@ -800,13 +802,13 @@ export class AgentRegistry extends WorkspaceRestorable { this.logger.info("Agent created successfully", { agentId, agentType, agentKind }); - agentStateLogger().logAgentCreate({ + this.stateLogger.logAgentCreate({ agentId: agentId, agentConfigId: config.agentConfigId, }); const [poolStats, versions] = this.getPoolStats(agentKind, agentType); - agentStateLogger().logPoolChange({ + this.stateLogger.logPoolChange({ agentTypeId: agentSomeIdToTypeValue(agent), poolStats, versions, @@ -854,12 +856,12 @@ export class AgentRegistry extends WorkspaceRestorable { agentConfigVersion, }); - agentStateLogger().logAgentDestroy({ + this.stateLogger.logAgentDestroy({ agentId, }); const [poolStats, versions] = this.getPoolStats(agentKind, agentType); - agentStateLogger().logPoolChange({ + this.stateLogger.logPoolChange({ agentTypeId: agentSomeIdToTypeValue(agent), poolStats, versions, diff --git a/src/agents/state/logger.ts b/src/agents/state/logger.ts index 2ba9912..c5e68a1 100644 --- a/src/agents/state/logger.ts +++ b/src/agents/state/logger.ts @@ -20,7 +20,24 @@ import { export const DEFAULT_NAME = "agent_state"; export const DEFAULT_PATH = ["logs"] as readonly string[]; -class AgentStateLogger extends BaseStateLogger { +export class AgentStateLogger extends BaseStateLogger { + private static instance?: AgentStateLogger; + + static init() { + if (this.instance) { + throw new Error(`Agent state logger is already initialized`); + } + this.instance = new AgentStateLogger(); + return this.instance; + } + + static getInstance() { + if (!this.instance) { + throw new Error(`Agent state logger wasn't initialized yet`); + } + return this.instance; + } + constructor(logPath?: string) { super(DEFAULT_PATH, DEFAULT_NAME, logPath); } @@ -137,11 +154,10 @@ class AgentStateLogger extends BaseStateLogger } } -let instance: AgentStateLogger | null = null; +export function init() { + return AgentStateLogger.init(); +} -export const agentStateLogger = () => { - if (!instance) { - instance = new AgentStateLogger(); - } - return instance; -}; +export function instance() { + return AgentStateLogger.getInstance(); +} diff --git a/src/agents/supervisor.ts b/src/agents/supervisor.ts index c3ab32d..7d2e680 100644 --- a/src/agents/supervisor.ts +++ b/src/agents/supervisor.ts @@ -8,8 +8,6 @@ export enum AgentTypes { BOSS = "boss", } -// (agentKind: ${agentKind}, agentType: ${agentType}, agentId: ${agentId}) - export const SUPERVISOR_INSTRUCTIONS = ( agentKind: string, agentType: string, diff --git a/src/agents/tool.ts b/src/agents/tool.ts index 719ec9d..13a5e28 100644 --- a/src/agents/tool.ts +++ b/src/agents/tool.ts @@ -18,196 +18,6 @@ import { } from "./registry/dto.js"; export const TOOL_NAME = "agent_registry"; - -// Comprehensive manual to use the tool. Source of truth for TOOL_RULES -export const TOOL_MANUAL = `# Agent Registry System Manual - -## Prerequisites - -To work with Agent Registry, ensure that: -* Tools Factory is registered for your agent kind -* Required agent configurations are defined -* You understand the agent lifecycle phases -* You have necessary permissions for agent management - -## Working Process - -### Creating Agent Configuration - -Agent configuration requires: -* **Agent Kind** - * Must be either supervisor or operator - * Defines the role and permissions level - -* **Agent Type** - * Must be unique identifier - * Describes specific function or purpose - -* **Instructions** - * Detailed behavior step-by-step guidelines - * Specific action protocols - * Performance expectations - * Expected output format - -* **Description** - * Purpose of existence - * Main responsibilities - * Expected outcomes - -* **Tools Access** - * List of specific tools - * Empty array for no tools - -* **Pool Settings** - * Maximum pool size - * Auto population preferences - -### Working with Tools - -Before agent creation: -* Get available tools for the agent kind and decide which should be used. - -### Understanding Pool System - -Pool management follows these principles: -* Each agent type has dedicated pool -* Pool size is fixed by configuration -* System handles all lifecycle events -* Automatic agent acquisition/release - -### Agent Lifecycle Phases - -Automatic system management includes: -* Creation of new agents when needed -* Pool management and optimization -* Task-based agent acquisition -* Post-task agent release -* Cleanup of unused agents - -### System Monitoring - -Available monitoring capabilities: -* Active agent tracking -* Pool utilization statistics -* Configuration version control -* Tool usage analytics - -## Key Considerations - -### Pool Management - -For optimal pool operation: -* Size pools based on workload -* Consider enabling auto-population -* Monitor utilization patterns -* Review resource allocation - -### Version Control - -Configuration versioning rules: -* All configurations are versioned -* New versions can be added -* Old versions remain accessible -* Version changes are tracked - -### Resource Optimization - -For best performance: -* Align pool sizes with needs -* Track resource consumption -* Monitor agent metrics -* Optimize tool distribution - -## System Limitations - -### Pool Constraints - -Fixed limitations include: -* Maximum size per agent type -* Automatic lifecycle only -* No manual agent management -* Fixed pool boundaries - -### Configuration Rules - -Remember that: -* Tools require pre-registration -* Agent types are predefined -* Configurations are immutable -* Changes require new versions - -## Best Practices - -### Configuration Setup - -When creating configurations: -* Write clear instructions -* Set realistic pool sizes -* Consider auto-population -* Define tool requirements - -### Resource Management - -For effective operation: -* Check pool utilization -* Monitor active agents -* Watch resource usage -* Review performance metrics - -### Error Prevention - -To minimize issues: -* Verify tool availability -* Validate configurations -* Monitor pool capacity -* Track error patterns - -## Important Rules - -Remember these key points: -* Configurations must exist before agents -* System manages all lifecycle events -* Monitor pools for health/utilization -* Track tools and configurations -* Regular performance review needed`; - -// Distilled manual for system prompt -export const TOOL_RULES = `# Agent Registry Rules - -You are working with an Agent Registry system. Follow these rules: - -1. Before requesting any agent operations, ensure a valid agent configuration exists that specifies: - * Agent Kind (supervisor/operator) - * Agent Type (unique identifier) - * Instructions for behavior - * Tools access settings - * Pool configuration - -2. Understand that agents are managed automatically: - * You cannot create or destroy agents directly - * Agents are acquired automatically when tasks start - * Agents are released automatically when tasks complete - * Pool sizes are fixed by configuration - -3. When working with tools: - * Only use tools registered for your agent kind - * Verify tool availability before tasks - * Null means all tools available - * Empty array means no tools allowed - * Agents doesn't need tools for llm capabilities - -4. For monitoring: - * Track active agents in your tasks - * Check pool statistics when needed - * Monitor tool availability - * Report any issues encountered - -5. Remember: - * Always check configurations before tasks - * Let the system handle agent lifecycle - * Work within pool size limits - * Use only available tools`; - export interface AgentRegistryToolInput extends BaseToolOptions { registry: AgentRegistry; } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2e8f3bb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,144 @@ +import { createAgent } from "@agents/agent-factory.js"; +import { AgentKindEnumSchema } from "@agents/registry/dto.js"; +import { AgentRegistry } from "@agents/registry/registry.js"; +import { AgentStateLogger } from "@agents/state/logger.js"; +import { TaskManager } from "@tasks/manager/manager.js"; +import { TaskStateLogger } from "@tasks/state/logger.js"; +import { WorkspaceManager } from "@workspaces/workspace-manager.js"; +import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; +import { supervisor, operator } from "./agents/index.js"; + +export * as agents from "./agents/index.js"; +export * as tasks from "./tasks/index.js"; +export * as workspaces from "./workspaces/index.js"; + +export async function createBeeSupervisor() { + // Reset audit logs + AgentStateLogger.init(); + TaskStateLogger.init(); + + // Setup workspace + WorkspaceManager.init(["workspaces"], "default"); + + const registry = new AgentRegistry({ + agentLifecycle: { + async onCreate( + config, + agentId, + toolsFactory, + ): Promise<{ agentId: string; instance: BeeAgent }> { + const { agentKind, agentType, instructions, description } = config; + const tools = config.tools == null ? toolsFactory.getAvailableToolsNames() : config.tools; + const instance = createAgent( + { + agentKind, + agentType, + agentId, + description, + instructions, + tools, + }, + toolsFactory, + ); + + return { agentId, instance }; + }, + async onDestroy(instance) { + instance.destroy(); + }, + }, + onAgentConfigCreated(agentKind, agentType) { + taskManager.registerAgentType(agentKind, agentType); + }, + }); + + const taskManager = new TaskManager( + async (taskRun, taskManager, { onAgentCreate, onAgentComplete, onAgentError }) => { + const agent = await registry.acquireAgent(taskRun.config.agentKind, taskRun.config.agentType); + onAgentCreate(taskRun.taskRunId, agent.agentId, taskManager); + const { instance } = agent; + const prompt = taskRun.taskRunInput; + instance + .run( + { prompt }, + { + execution: { + maxIterations: 8, + maxRetriesPerStep: 2, + totalMaxRetries: 10, + }, + }, + ) + // .observe((emitter) => { + // emitter.on("update", (data, meta) => { + // reader.write( + // `${(meta.creator as any).input.meta.name} 🤖 (${data.update.key}) :`, + // data.update.value, + // ); + // }); + // emitter.on("error", (data, meta) => { + // reader.write( + // `${(meta.creator as any).input.meta.name} 🤖 (${data.error.name}) :`, + // data.error.message, + // ); + // }); + // }) + .then((resp) => + onAgentComplete(resp.result.text, taskRun.taskRunId, agent.agentId, taskManager), + ) + .catch((err) => { + onAgentError(err, taskRun.taskRunId, agent.agentId, taskManager); + }) + .finally(() => { + registry.releaseAgent(agent.agentId); + }); + }, + ); + + registry.registerToolsFactories([ + ["supervisor", new supervisor.ToolsFactory(registry, taskManager)], + ["operator", new operator.ToolsFactory()], + ]); + + registry.restore(); + + if ( + !registry.isAgentConfigExists(AgentKindEnumSchema.Enum.supervisor, supervisor.AgentTypes.BOSS) + ) { + registry.createAgentConfig({ + autoPopulatePool: false, + agentKind: AgentKindEnumSchema.Enum.supervisor, + agentType: supervisor.AgentTypes.BOSS, + instructions: "", + tools: registry.getToolsFactory(AgentKindEnumSchema.Enum.supervisor).getAvailableToolsNames(), + description: "The boss supervisor agent that control whole app.", + maxPoolSize: 1, + }); + } + + const { instance: supervisorAgent, agentId: supervisorAgentId } = await registry.acquireAgent( + AgentKindEnumSchema.Enum.supervisor, + supervisor.AgentTypes.BOSS, + ); + + taskManager.registerAdminAgent(supervisorAgentId); + taskManager.restore(supervisorAgentId); + + // Can you create tasks to write poem about: sun, earth, mars and assign them to the right agent type and run them? + // Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types run all tasks and give me the created poems when it will be all finished? + // Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types? + + // Can you create agent type that will write the best poems on different topics with the pool size 2? + // Can you create tasks to create poem about: sun, night, water, hell, love, hate. Assign them to the right agent types? + // Can you runt these tasks? + // Can you list their results? + + // Can you generate poem for each of these topics: love, day, night? + // Can you get list of articles about each of these topics: deepseek, interstellar engine, agi? + + // Can you create different kinds of specialized agents that will do a research on different aspects of person profile from internet? You should be very specific and explanatory in their instructions. Don't create any tasks. + // Base on these agents can you prepare related tasks. And one extra agent and task that will summarize task outputs other tasks. + // Can you create a personal profile of Dario Gil? + + return supervisorAgent; +} diff --git a/src/main.ts b/src/main.ts index 62aac96..b5d8fcf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,26 +1,23 @@ +import "dotenv/config.js"; import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; import { FrameworkError } from "bee-agent-framework/errors"; -import "dotenv/config.js"; import { createAgent } from "./agents/agent-factory.js"; -import * as operator from "./agents/operator.js"; import { AgentKindEnumSchema } from "./agents/registry/dto.js"; import { AgentRegistry } from "./agents/registry/registry.js"; -import { agentStateLogger } from "./agents/state/logger.js"; -import * as supervisor from "./agents/supervisor.js"; +import { TaskManager } from "@tasks/manager/manager.js"; +import { AgentStateLogger } from "@agents/state/logger.js"; +import { TaskStateLogger } from "@tasks/state/logger.js"; +import { supervisor, operator } from "@agents/index.js"; import { createConsoleReader } from "./helpers/reader.js"; -import { TaskManager } from "./tasks/manager/manager.js"; -import { taskStateLogger } from "./tasks/state/logger.js"; -import { WorkspaceManager } from "./workspace/workspace-manager.js"; +import { WorkspaceManager } from "@workspaces/workspace-manager.js"; // Reset audit logs -agentStateLogger(); -taskStateLogger(); +AgentStateLogger.init(); +TaskStateLogger.init(); -const workspaceManager = WorkspaceManager.getInstance(); // Setup workspace -workspaceManager.setWorkspaceDirPath(["workspaces"]); -workspaceManager.setWorkspace("default"); +WorkspaceManager.init(["workspaces"], "default"); const registry = new AgentRegistry({ agentLifecycle: { diff --git a/src/tasks/index.ts b/src/tasks/index.ts new file mode 100644 index 0000000..77670f5 --- /dev/null +++ b/src/tasks/index.ts @@ -0,0 +1,2 @@ +export * as tasksTool from "./tool.js"; +export * as tasksStateLogger from "./state/logger.js"; diff --git a/src/tasks/manager/manager.ts b/src/tasks/manager/manager.ts index ed95e3f..1a789cb 100644 --- a/src/tasks/manager/manager.ts +++ b/src/tasks/manager/manager.ts @@ -10,11 +10,7 @@ import { } from "src/access-control/resources-access-control.js"; import { agentSomeIdToTypeValue } from "src/agents/agent-id.js"; import { AgentIdValue, AgentKindEnum, AgentTypeValue } from "src/agents/registry/dto.js"; -import { agentStateLogger } from "src/agents/state/logger.js"; -import { taskStateLogger } from "src/tasks/state/logger.js"; import { updateDeepPartialObject } from "src/utils/objects.js"; -import { WorkspaceResource } from "src/workspace/workspace-manager.js"; -import { WorkspaceRestorable } from "src/workspace/workspace-restorable.js"; import { taskConfigIdToValue, taskRunIdToString, taskSomeIdToTypeValue } from "../task-id.js"; import { isTaskRunActiveStatus, @@ -33,6 +29,10 @@ import { TaskRunTerminalStatusEnum, TaskTypeValue, } from "./dto.js"; +import { TaskStateLogger } from "@tasks/state/logger.js"; +import { AgentStateLogger } from "@agents/state/logger.js"; +import { WorkspaceResource } from "@workspaces/workspace-manager.js"; +import { WorkspaceRestorable } from "@workspaces/workspace-restorable.js"; export type TaskRunRuntime = TaskRun & { intervalId: NodeJS.Timeout | null; @@ -57,6 +57,8 @@ export class TaskManager extends WorkspaceRestorable { private taskStartIntervalId: NodeJS.Timeout | null = null; private registeredAgentTypes = new Map(); private ac: ResourcesAccessControl; + private stateLogger: TaskStateLogger; + private agentStateLogger: AgentStateLogger; constructor( private onTaskStart: ( @@ -91,9 +93,10 @@ export class TaskManager extends WorkspaceRestorable { ) { super(TASK_MANAGER_CONFIG_PATH, TASK_MANAGER_USER); this.logger.info("Initializing TaskManager"); + this.stateLogger = TaskStateLogger.getInstance(); + this.agentStateLogger = AgentStateLogger.getInstance(); this.ac = new ResourcesAccessControl(this.constructor.name, [TASK_MANAGER_USER]); - this.ac.createResource(TASK_MANAGER_RESOURCE, TASK_MANAGER_USER, TASK_MANAGER_USER); this.options = { @@ -184,7 +187,7 @@ export class TaskManager extends WorkspaceRestorable { } types.push(agentType); } - taskStateLogger().logAgentTypeRegister({ + this.stateLogger.logAgentTypeRegister({ agentTypeId: agentSomeIdToTypeValue({ agentKind, agentType }), }); } @@ -405,7 +408,7 @@ export class TaskManager extends WorkspaceRestorable { this.ac.createResource(taskConfigId, ownerAgentId, actingAgentId); this.ac.createPermissions(taskConfigId, ownerAgentId, READ_EXECUTE_ACCESS, actingAgentId); - taskStateLogger().logTaskConfigCreate({ + this.stateLogger.logTaskConfigCreate({ taskConfigId, taskType: taskSomeIdToTypeValue(configVersioned), config: configVersioned, @@ -537,7 +540,7 @@ export class TaskManager extends WorkspaceRestorable { actingAgentId, ); - taskStateLogger().logTaskConfigUpdate({ + this.stateLogger.logTaskConfigUpdate({ taskType: taskSomeIdToTypeValue(newConfigVersion), taskConfigId: newConfigVersion.taskConfigId, config: newConfigVersion, @@ -582,7 +585,7 @@ export class TaskManager extends WorkspaceRestorable { taskConfigVersion, }); - taskStateLogger().logTaskConfigDestroy({ + this.stateLogger.logTaskConfigDestroy({ taskConfigId, taskType, }); @@ -590,7 +593,7 @@ export class TaskManager extends WorkspaceRestorable { this.ac.removeResource(taskConfigId, actingAgentId); const [poolStats, versions] = this.getPoolStats(taskKind, taskType, actingAgentId); - taskStateLogger().logPoolChange({ + this.stateLogger.logPoolChange({ taskTypeId: taskSomeIdToTypeValue({ taskKind, taskType }), poolStats, versions, @@ -676,7 +679,7 @@ export class TaskManager extends WorkspaceRestorable { ...taskRun, }); - taskStateLogger().logTaskRunCreate({ + this.stateLogger.logTaskRunCreate({ taskConfigId: taskRun.config.taskConfigId, taskRunId, taskRun, @@ -694,7 +697,7 @@ export class TaskManager extends WorkspaceRestorable { this.logger.trace("Added task to pool", { taskKind, taskType, taskConfigVersion, taskRunId }); const [poolStats, versions] = this.getPoolStats(taskKind, taskType, actingAgentId); - taskStateLogger().logPoolChange({ + this.stateLogger.logPoolChange({ taskTypeId: taskSomeIdToTypeValue({ taskKind, taskType }), poolStats, versions, @@ -867,12 +870,12 @@ export class TaskManager extends WorkspaceRestorable { taskConfigVersion, }); - taskStateLogger().logTaskRunDestroy({ + this.stateLogger.logTaskRunDestroy({ taskRunId, }); const [poolStats, versions] = this.getPoolStats(taskKind, taskType, actingAgentId); - taskStateLogger().logPoolChange({ + this.stateLogger.logPoolChange({ taskTypeId: taskSomeIdToTypeValue(taskRun), poolStats, versions, @@ -903,7 +906,7 @@ export class TaskManager extends WorkspaceRestorable { currentAgentId: actingAgentId, }); - agentStateLogger().logTaskAssigned({ + this.agentStateLogger.logTaskAssigned({ agentId: actingAgentId, assignment: clone(omit(taskRun, ["intervalId"])), assignmentId: taskRunId, @@ -946,7 +949,7 @@ export class TaskManager extends WorkspaceRestorable { this.ac.removePermissions(taskRunId, actingAgentId, TASK_MANAGER_USER); - agentStateLogger().logTaskUnassigned({ + this.agentStateLogger.logTaskUnassigned({ agentId: actingAgentId, assignmentId: taskRunId, unassignedAt, @@ -976,11 +979,11 @@ export class TaskManager extends WorkspaceRestorable { update: Partial, ): TaskRun { updateDeepPartialObject(taskRun, update); - taskStateLogger().logTaskRunUpdate({ taskRunId, taskRun: update }); + this.stateLogger.logTaskRunUpdate({ taskRunId, taskRun: update }); if (update.status) { const { taskKind, taskType } = taskRun; const [poolStats, versions] = this.getPoolStats(taskKind, taskType, TASK_MANAGER_USER); - taskStateLogger().logPoolChange({ + this.stateLogger.logPoolChange({ taskTypeId: taskSomeIdToTypeValue({ taskKind, taskType }), poolStats, versions, @@ -1173,8 +1176,8 @@ export class TaskManager extends WorkspaceRestorable { ): void { const taskRun = this.getTaskRun(taskRunId, actingAgentId); taskRun.history.push(entry); - taskStateLogger().logTaskHistoryEntryCreate({ taskRunId, entry }); - agentStateLogger().logTaskHistoryEntry({ + this.stateLogger.logTaskHistoryEntryCreate({ taskRunId, entry }); + this.agentStateLogger.logTaskHistoryEntry({ agentId: actingAgentId, entry, assignmentId: taskRunId, diff --git a/src/tasks/state/logger.ts b/src/tasks/state/logger.ts index 4998c34..d1d4cf9 100644 --- a/src/tasks/state/logger.ts +++ b/src/tasks/state/logger.ts @@ -16,7 +16,24 @@ import { export const DEFAULT_NAME = "task_state"; export const DEFAULT_PATH = ["logs"] as readonly string[]; -class TaskStateLogger extends BaseStateLogger { +export class TaskStateLogger extends BaseStateLogger { + private static instance?: TaskStateLogger; + + static init() { + if (this.instance) { + throw new Error(`Task state logger is already initialized`); + } + this.instance = new TaskStateLogger(); + return this.instance; + } + + static getInstance() { + if (!this.instance) { + throw new Error(`Task state logger wasn't initialized yet`); + } + return this.instance; + } + constructor(logPath?: string) { super(DEFAULT_PATH, DEFAULT_NAME, logPath); } @@ -103,12 +120,10 @@ class TaskStateLogger extends BaseStateLogger { } } -let instance: TaskStateLogger | null = null; +export function init() { + return TaskStateLogger.init(); +} -// Export singleton instance -export const taskStateLogger = () => { - if (!instance) { - instance = new TaskStateLogger(); - } - return instance; -}; +export function instance() { + return TaskStateLogger.getInstance(); +} diff --git a/src/ui/index.ts b/src/ui/index.ts new file mode 100644 index 0000000..c9b73fe --- /dev/null +++ b/src/ui/index.ts @@ -0,0 +1,3 @@ +export * from "./agent-monitor/monitor.js"; +export * from "./task-monitor/monitor.js"; +export * from "./monitor.js"; diff --git a/src/ui/main.ts b/src/ui/main.ts new file mode 100644 index 0000000..1e1355a --- /dev/null +++ b/src/ui/main.ts @@ -0,0 +1,3 @@ +import { Monitor } from "./monitor.js"; + +new Monitor().start(); diff --git a/src/ui/monitor.ts b/src/ui/monitor.ts new file mode 100644 index 0000000..f522b0e --- /dev/null +++ b/src/ui/monitor.ts @@ -0,0 +1,49 @@ +import blessed from "blessed"; +import { TaskMonitor } from "./task-monitor/monitor.js"; +import { AgentMonitor } from "./agent-monitor/monitor.js"; +import { BaseMonitor } from "./base/monitor.js"; + +export class Monitor extends BaseMonitor { + private agentMonitor: AgentMonitor; + private taskMonitor: TaskMonitor; + + constructor(title = "Bee Supervisor Monitor") { + super({ title }); + this.agentMonitor = new AgentMonitor({ + screen: this.screen, + parent: blessed.box({ + parent: this.screen, + width: "50%", + height: "100%", + left: 0, + top: 0, + mouse: true, + keys: true, + vi: true, + border: { type: "bg" }, + label: "■■■ AGENT MONITOR ■■■", + }), + }); + + this.taskMonitor = new TaskMonitor({ + screen: this.screen, + parent: blessed.box({ + parent: this.screen, + width: "50%", + height: "100%", + left: "50%", + top: 0, + mouse: true, + keys: true, + vi: true, + border: { type: "bg" }, + label: "■■■ TASK MONITOR ■■■", + }), + }); + } + + start() { + this.agentMonitor.start(); + this.taskMonitor.start(); + } +} diff --git a/src/ui/supervisor-monitor.ts b/src/ui/supervisor-monitor.ts deleted file mode 100644 index 3aa5cce..0000000 --- a/src/ui/supervisor-monitor.ts +++ /dev/null @@ -1,41 +0,0 @@ -import blessed from "blessed"; -import { TaskMonitor } from "./task-monitor/monitor.js"; -import { AgentMonitor } from "./agent-monitor/monitor.js"; - -const screen = blessed.screen({ - smartCSR: true, - title: "Supervisor UI", - debug: true, -}); - -new AgentMonitor({ - screen, - parent: blessed.box({ - parent: screen, - width: "50%", - height: "100%", - left: 0, - top: 0, - mouse: true, - keys: true, - vi: true, - border: { type: "bg" }, - label: "■■■ AGENT MONITOR ■■■", - }), -}).start(); - -new TaskMonitor({ - screen, - parent: blessed.box({ - parent: screen, - width: "50%", - height: "100%", - left: "50%", - top: 0, - mouse: true, - keys: true, - vi: true, - border: { type: "bg" }, - label: "■■■ TASK MONITOR ■■■", - }), -}).start(); diff --git a/src/workspaces/index.ts b/src/workspaces/index.ts new file mode 100644 index 0000000..4eb9d42 --- /dev/null +++ b/src/workspaces/index.ts @@ -0,0 +1 @@ +export * as workspaces from "./workspace-manager.js"; diff --git a/src/workspace/workspace-manager.ts b/src/workspaces/workspace-manager.ts similarity index 94% rename from src/workspace/workspace-manager.ts rename to src/workspaces/workspace-manager.ts index fdb3d27..b0b428d 100644 --- a/src/workspace/workspace-manager.ts +++ b/src/workspaces/workspace-manager.ts @@ -43,6 +43,23 @@ export class WorkspaceManager extends EventEmitter { private _workspacePath?: string; private resources = new Map(); + static init(dirPath: string[], workspace: string) { + if (this.instance) { + throw new Error(`Workspace manager is already initialized`); + } + this.instance = new WorkspaceManager(); + this.instance.setWorkspaceDirPath(dirPath); + this.instance.setWorkspace(workspace); + return this.instance; + } + + static getInstance() { + if (!this.instance) { + throw new Error(`Workspace manager wasn't initialized yet`); + } + return this.instance; + } + get workspaceName() { if (!this._workspaceName) { throw Error(`Workspace wasn't set yet`); @@ -76,15 +93,6 @@ export class WorkspaceManager extends EventEmitter { this._workspacesDirPath = joinedPath; } - static getInstance() { - let instance = WorkspaceManager.instance; - if (!instance) { - instance = WorkspaceManager.instance = new WorkspaceManager(); - } - - return instance; - } - private constructor() { super(); this.logger = Logger.root.child({ name: "WorkspaceManager" }); diff --git a/src/workspace/workspace-restorable.ts b/src/workspaces/workspace-restorable.ts similarity index 93% rename from src/workspace/workspace-restorable.ts rename to src/workspaces/workspace-restorable.ts index 5828e91..cff50a3 100644 --- a/src/workspace/workspace-restorable.ts +++ b/src/workspaces/workspace-restorable.ts @@ -1,6 +1,6 @@ import { Logger } from "bee-agent-framework"; import { AgentIdValue } from "src/agents/registry/dto.js"; -import { WorkspaceManager, WorkspaceResource } from "src/workspace/workspace-manager.js"; +import { WorkspaceManager, WorkspaceResource } from "./workspace-manager.js"; export abstract class WorkspaceRestorable { protected readonly logger: Logger; diff --git a/start.sh b/start.sh deleted file mode 100755 index 0c7dfd9..0000000 --- a/start.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash - -# Default number of operators if not specified -OPERATORS_COUNT=${1:-2} - -# Check if tmux is installed -if ! command -v tmux &> /dev/null; then - echo "tmux is not installed. Please install it first." - exit 1 -fi - -# Kill existing session if it exists -tmux kill-session -t agent_system 2>/dev/null - -# Create logs directory if it doesn't exist -mkdir -p logs - -# Start a new session -tmux new-session -d -s agent_system -n 'Agent System' - -# First split horizontally at 66% for the top section -tmux split-window -v -p 34 - -# Now work on the top section (pane 0) -# Split top section vertically -tmux select-pane -t 0 -tmux split-window -h -p 50 - -# Split top-left pane horizontally -tmux select-pane -t 0 -tmux split-window -v -p 50 - -# Split top-right pane horizontally -tmux select-pane -t 2 -tmux split-window -v -p 50 - -# Now we have our 2x2 grid in the top section: -# 0: Interactive (top-left) -# 1: Supervisor Log (bottom-left) -# 2: Agent Registry (top-right) -# 3: Task Manager (bottom-right) - -# Configure the top section panes -tmux select-pane -t 0 -tmux send-keys 'echo "🤖 Starting supervisor interaction..." && sleep 2 && NODE_ENV=development node start' C-m - -tmux select-pane -t 1 -tmux send-keys 'tail -f logs/supervisor_agents.log | pino-pretty' C-m - -tmux select-pane -t 2 -tmux send-keys 'tail -f logs/agent_registry.log | pino-pretty' C-m - -tmux select-pane -t 3 -tmux send-keys 'tail -f logs/task_manager.log | pino-pretty' C-m - -# Now handle the bottom section for operators -# Split the bottom pane horizontally for each operator -tmux select-pane -t 4 -for ((i=1; i Date: Sun, 23 Feb 2025 10:34:25 +0100 Subject: [PATCH 10/13] chore: packages refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- package-lock.json | 122 +++++++++++++++ package.json | 1 + src/agents/index.ts | 10 +- src/agents/operator.ts | 2 +- src/agents/registry/index.ts | 2 + src/agents/registry/registry.ts | 11 +- src/agents/{ => registry}/tool.ts | 4 +- src/agents/supervisor.ts | 35 ++++- src/base/tools-factory.ts | 8 +- src/index.ts | 19 ++- src/main.ts | 143 +----------------- src/tasks/manager/manager.ts | 4 +- src/workspaces/index.ts | 8 +- src/workspaces/manager/index.ts | 1 + .../manager.ts} | 9 +- src/workspaces/restore/index.ts | 1 + .../restorable.ts} | 2 +- 17 files changed, 214 insertions(+), 168 deletions(-) create mode 100644 src/agents/registry/index.ts rename src/agents/{ => registry}/tool.ts (98%) create mode 100644 src/workspaces/manager/index.ts rename src/workspaces/{workspace-manager.ts => manager/manager.ts} (97%) create mode 100644 src/workspaces/restore/index.ts rename src/workspaces/{workspace-restorable.ts => restore/restorable.ts} (93%) diff --git a/package-lock.json b/package-lock.json index 33416b2..44e38af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@google-cloud/vertexai": "^1.9.2", "@ibm-generative-ai/node-sdk": "^3.2.4", + "@modelcontextprotocol/sdk": "^1.5.0", "@opentelemetry/sdk-node": "^0.57.0", "@types/blessed": "^0.1.25", "bee-agent-framework": "^0.0.61", @@ -1634,6 +1635,21 @@ "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.5.0.tgz", + "integrity": "sha512-IJ+5iVVs8FCumIHxWqpwgkwOzyhtHVKy45s6Ug7Dv0MfRpaYisH8QQ87rIWeWdOzlk8sfhitZ7HCyQZk7d6b8w==", + "dependencies": { + "content-type": "^1.0.5", + "eventsource": "^3.0.2", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3984,6 +4000,14 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -4261,6 +4285,14 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", @@ -4345,6 +4377,14 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -4708,6 +4748,25 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.5.tgz", + "integrity": "sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==", + "dependencies": { + "eventsource-parser": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", + "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -5285,6 +5344,21 @@ } ] }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-status-codes": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", @@ -5422,6 +5496,11 @@ "url": "https://www.buymeacoffee.com/2tmRKi9" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/is-core-module": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", @@ -6734,6 +6813,20 @@ "node": ">=14.18.0" } }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", @@ -7061,6 +7154,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7161,6 +7259,14 @@ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", @@ -7503,6 +7609,14 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -7643,6 +7757,14 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", diff --git a/package.json b/package.json index 81b548c..aaf59ea 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "dependencies": { "@google-cloud/vertexai": "^1.9.2", "@ibm-generative-ai/node-sdk": "^3.2.4", + "@modelcontextprotocol/sdk": "^1.5.0", "@opentelemetry/sdk-node": "^0.57.0", "@types/blessed": "^0.1.25", "bee-agent-framework": "^0.0.61", diff --git a/src/agents/index.ts b/src/agents/index.ts index 0c74d2a..59c1e10 100644 --- a/src/agents/index.ts +++ b/src/agents/index.ts @@ -1,4 +1,6 @@ -export * as agentsTool from "./tool.js"; -export * as agentsStateLogger from "./state/logger.js"; -export * as supervisor from "./supervisor.js"; -export * as operator from "./operator.js"; +import * as agentRegistry from "./registry/index.js"; +import * as stateLogger from "./state/logger.js"; +import * as supervisor from "./supervisor.js"; +import * as operator from "./operator.js"; + +export { agentRegistry, stateLogger, supervisor, operator }; diff --git a/src/agents/operator.ts b/src/agents/operator.ts index 2ebd561..4940502 100644 --- a/src/agents/operator.ts +++ b/src/agents/operator.ts @@ -3,7 +3,7 @@ import { DuckDuckGoSearchTool } from "bee-agent-framework/tools/search/duckDuckG import { BaseToolsFactory, ToolFactoryMethod } from "src/base/tools-factory.js"; export class ToolsFactory extends BaseToolsFactory { - getFactoriesMethods(): ToolFactoryMethod[] { + async getFactoriesMethods(): Promise { return [() => new DuckDuckGoSearchTool(), () => new ArXivTool()]; } } diff --git a/src/agents/registry/index.ts b/src/agents/registry/index.ts new file mode 100644 index 0000000..090c838 --- /dev/null +++ b/src/agents/registry/index.ts @@ -0,0 +1,2 @@ +export * from "./registry.js"; +export * from "./tool.js"; diff --git a/src/agents/registry/registry.ts b/src/agents/registry/registry.ts index 97187ca..eeef9e5 100644 --- a/src/agents/registry/registry.ts +++ b/src/agents/registry/registry.ts @@ -22,8 +22,8 @@ import { AgentWithInstance, } from "./dto.js"; import { AgentStateLogger } from "@agents/state/logger.js"; -import { WorkspaceResource } from "@workspaces/workspace-manager.js"; -import { WorkspaceRestorable } from "@workspaces/workspace-restorable.js"; +import { WorkspaceRestorable } from "@workspaces/restore/restorable.js"; +import { WorkspaceResource } from "@workspaces/manager/index.js"; /** * Callbacks for managing agent lifecycle events. @@ -164,14 +164,15 @@ export class AgentRegistry extends WorkspaceRestorable { * Register tools factory for a specific agent type * @param tuples */ - registerToolsFactories(tuples: [AgentKindEnum, BaseToolsFactory][]) { - tuples.map(([agentKind, factory]) => { + async registerToolsFactories(tuples: [AgentKindEnum, BaseToolsFactory][]) { + for (const [agentKind, factory] of tuples) { + await factory.init(); this.toolsFactory.set(agentKind, factory); this.stateLogger.logAvailableTools({ agentKindId: agentSomeIdToKindValue({ agentKind }), availableTools: factory.getAvailableTools(), }); - }); + } } private getAgentKindPoolMap(agentKind: AgentKindEnum) { diff --git a/src/agents/tool.ts b/src/agents/registry/tool.ts similarity index 98% rename from src/agents/tool.ts rename to src/agents/registry/tool.ts index 13a5e28..aa32686 100644 --- a/src/agents/tool.ts +++ b/src/agents/registry/tool.ts @@ -7,7 +7,7 @@ import { ToolInput, } from "bee-agent-framework/tools/base"; import { z } from "zod"; -import { AgentInstanceRef, AgentRegistry } from "./registry/registry.js"; +import { AgentInstanceRef, AgentRegistry } from "./registry.js"; import { Agent, AgentConfig, @@ -15,7 +15,7 @@ import { AgentConfigSchema, AgentKindEnumSchema, AvailableTool, -} from "./registry/dto.js"; +} from "./dto.js"; export const TOOL_NAME = "agent_registry"; export interface AgentRegistryToolInput extends BaseToolOptions { diff --git a/src/agents/supervisor.ts b/src/agents/supervisor.ts index 7d2e680..04e41eb 100644 --- a/src/agents/supervisor.ts +++ b/src/agents/supervisor.ts @@ -1,8 +1,9 @@ +import { TaskManager } from "@tasks/manager/manager.js"; +import { TaskManagerTool, TOOL_NAME as taskManagerToolName } from "@tasks/tool.js"; +import { WorkspaceManager } from "@workspaces/manager/manager.js"; import { BaseToolsFactory, ToolFactoryMethod } from "src/base/tools-factory.js"; -import { TaskManager } from "src/tasks/manager/manager.js"; -import { TaskManagerTool, TOOL_NAME as taskManagerToolName } from "src/tasks/tool.js"; -import { AgentRegistry } from "./registry/registry.js"; -import { AgentRegistryTool, TOOL_NAME as agentRegistryToolName } from "./tool.js"; +import { AgentRegistry } from "./registry/index.js"; +import { AgentRegistryTool, TOOL_NAME as agentRegistryToolName } from "./registry/tool.js"; export enum AgentTypes { BOSS = "boss", @@ -54,14 +55,38 @@ export class ToolsFactory extends BaseToolsFactory { constructor( protected registry: AgentRegistry, protected taskManager: TaskManager, + protected workdir: string, ) { super(); } - getFactoriesMethods(): ToolFactoryMethod[] { + async getFactoriesMethods(): Promise { return [ () => new AgentRegistryTool({ registry: this.registry }), () => new TaskManagerTool({ taskManager: this.taskManager }), ]; } } + +export class Workdir { + static path = ["workdir"] as const; + + static getWorkdirPath() { + const workdirPath = WorkspaceManager.getInstance().getWorkspacePath( + Workdir.getWorkspacePathInput(), + ); + + return workdirPath; + } + + private static getWorkspacePathInput() { + return { + kind: "directory", + path: Workdir.path, + } as const; + } + + static registerWorkdir(supervisorId: string) { + WorkspaceManager.getInstance().registerResource(Workdir.getWorkspacePathInput(), supervisorId); + } +} diff --git a/src/base/tools-factory.ts b/src/base/tools-factory.ts index fbf8144..cb4d9c8 100644 --- a/src/base/tools-factory.ts +++ b/src/base/tools-factory.ts @@ -11,7 +11,11 @@ export abstract class BaseToolsFactory { constructor() { this.logger = Logger.root.child({ name: this.constructor.name }); - for (const factory of this.getFactoriesMethods()) { + } + + async init() { + const methods = await this.getFactoriesMethods(); + for (const factory of methods) { const product = factory(); this.availableTools.set(product.name, { name: product.name, @@ -21,7 +25,7 @@ export abstract class BaseToolsFactory { } } - abstract getFactoriesMethods(): ToolFactoryMethod[]; + abstract getFactoriesMethods(): Promise; getAvailableTools(): AvailableTool[] { return Array.from(this.availableTools.values()); diff --git a/src/index.ts b/src/index.ts index 2e8f3bb..cb1b13b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { AgentRegistry } from "@agents/registry/registry.js"; import { AgentStateLogger } from "@agents/state/logger.js"; import { TaskManager } from "@tasks/manager/manager.js"; import { TaskStateLogger } from "@tasks/state/logger.js"; -import { WorkspaceManager } from "@workspaces/workspace-manager.js"; +import { WorkspaceManager } from "@workspaces/manager/manager.js"; import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; import { supervisor, operator } from "./agents/index.js"; @@ -12,13 +12,13 @@ export * as agents from "./agents/index.js"; export * as tasks from "./tasks/index.js"; export * as workspaces from "./workspaces/index.js"; -export async function createBeeSupervisor() { +export async function createBeeSupervisor(workspace = "default") { // Reset audit logs AgentStateLogger.init(); TaskStateLogger.init(); // Setup workspace - WorkspaceManager.init(["workspaces"], "default"); + WorkspaceManager.init(["workspaces"], workspace); const registry = new AgentRegistry({ agentLifecycle: { @@ -95,8 +95,15 @@ export async function createBeeSupervisor() { }, ); - registry.registerToolsFactories([ - ["supervisor", new supervisor.ToolsFactory(registry, taskManager)], + await registry.registerToolsFactories([ + [ + "supervisor", + new supervisor.ToolsFactory( + registry, + taskManager, + supervisor.Workdir.getWorkdirPath().validPath, + ), + ], ["operator", new operator.ToolsFactory()], ]); @@ -124,6 +131,8 @@ export async function createBeeSupervisor() { taskManager.registerAdminAgent(supervisorAgentId); taskManager.restore(supervisorAgentId); + supervisor.Workdir.registerWorkdir(supervisorAgentId); + // Can you create tasks to write poem about: sun, earth, mars and assign them to the right agent type and run them? // Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types run all tasks and give me the created poems when it will be all finished? // Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types? diff --git a/src/main.ts b/src/main.ts index b5d8fcf..e19ac63 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,144 +1,11 @@ -import "dotenv/config.js"; -import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; -import { FrameworkError } from "bee-agent-framework/errors"; - -import { createAgent } from "./agents/agent-factory.js"; -import { AgentKindEnumSchema } from "./agents/registry/dto.js"; -import { AgentRegistry } from "./agents/registry/registry.js"; -import { TaskManager } from "@tasks/manager/manager.js"; -import { AgentStateLogger } from "@agents/state/logger.js"; -import { TaskStateLogger } from "@tasks/state/logger.js"; -import { supervisor, operator } from "@agents/index.js"; +import { FrameworkError } from "bee-agent-framework"; import { createConsoleReader } from "./helpers/reader.js"; -import { WorkspaceManager } from "@workspaces/workspace-manager.js"; - -// Reset audit logs -AgentStateLogger.init(); -TaskStateLogger.init(); - -// Setup workspace -WorkspaceManager.init(["workspaces"], "default"); - -const registry = new AgentRegistry({ - agentLifecycle: { - async onCreate( - config, - agentId, - toolsFactory, - ): Promise<{ agentId: string; instance: BeeAgent }> { - const { agentKind, agentType, instructions, description } = config; - const tools = config.tools == null ? toolsFactory.getAvailableToolsNames() : config.tools; - const instance = createAgent( - { - agentKind, - agentType, - agentId, - description, - instructions, - tools, - }, - toolsFactory, - ); - - return { agentId, instance }; - }, - async onDestroy(instance) { - instance.destroy(); - }, - }, - onAgentConfigCreated(agentKind, agentType) { - taskManager.registerAgentType(agentKind, agentType); - }, -}); - -const taskManager = new TaskManager( - async (taskRun, taskManager, { onAgentCreate, onAgentComplete, onAgentError }) => { - const agent = await registry.acquireAgent(taskRun.config.agentKind, taskRun.config.agentType); - onAgentCreate(taskRun.taskRunId, agent.agentId, taskManager); - const { instance } = agent; - const prompt = taskRun.taskRunInput; - instance - .run( - { prompt }, - { - execution: { - maxIterations: 8, - maxRetriesPerStep: 2, - totalMaxRetries: 10, - }, - }, - ) - .observe((emitter) => { - emitter.on("update", (data, meta) => { - reader.write( - `${(meta.creator as any).input.meta.name} 🤖 (${data.update.key}) :`, - data.update.value, - ); - }); - emitter.on("error", (data, meta) => { - reader.write( - `${(meta.creator as any).input.meta.name} 🤖 (${data.error.name}) :`, - data.error.message, - ); - }); - }) - .then((resp) => - onAgentComplete(resp.result.text, taskRun.taskRunId, agent.agentId, taskManager), - ) - .catch((err) => { - onAgentError(err, taskRun.taskRunId, agent.agentId, taskManager); - }) - .finally(() => { - registry.releaseAgent(agent.agentId); - }); - }, -); - -registry.registerToolsFactories([ - ["supervisor", new supervisor.ToolsFactory(registry, taskManager)], - ["operator", new operator.ToolsFactory()], -]); - -registry.restore(); - -if ( - !registry.isAgentConfigExists(AgentKindEnumSchema.Enum.supervisor, supervisor.AgentTypes.BOSS) -) { - registry.createAgentConfig({ - autoPopulatePool: false, - agentKind: AgentKindEnumSchema.Enum.supervisor, - agentType: supervisor.AgentTypes.BOSS, - instructions: "", - tools: registry.getToolsFactory(AgentKindEnumSchema.Enum.supervisor).getAvailableToolsNames(), - description: "The boss supervisor agent that control whole app.", - maxPoolSize: 1, - }); -} - -const { instance: supervisorAgent, agentId: supervisorAgentId } = await registry.acquireAgent( - AgentKindEnumSchema.Enum.supervisor, - supervisor.AgentTypes.BOSS, -); - -taskManager.registerAdminAgent(supervisorAgentId); -taskManager.restore(supervisorAgentId); - -// Can you create tasks to write poem about: sun, earth, mars and assign them to the right agent type and run them? -// Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types run all tasks and give me the created poems when it will be all finished? -// Can you create agent type that will write the best poems on different topics, then create tasks to create poem about: sun, night, water. Assign them to the right agent types? - -// Can you create agent type that will write the best poems on different topics with the pool size 2? -// Can you create tasks to create poem about: sun, night, water, hell, love, hate. Assign them to the right agent types? -// Can you runt these tasks? -// Can you list their results? - -// Can you generate poem for each of these topics: love, day, night? -// Can you get list of articles about each of these topics: deepseek, interstellar engine, agi? +import { createBeeSupervisor } from "./index.js"; -// Can you create different kinds of specialized agents that will do a research on different aspects of person profile from internet? You should be very specific and explanatory in their instructions. Don't create any tasks. -// Base on these agents can you prepare related tasks. And one extra agent and task that will summarize task outputs other tasks. -// Can you create a personal profile of Dario Gil? +const args = process.argv.slice(2); +const workspace = args[0]; +const supervisorAgent = await createBeeSupervisor(workspace); const reader = createConsoleReader({ fallback: "What is the current weather in Las Vegas?" }); for await (const { prompt } of reader) { try { diff --git a/src/tasks/manager/manager.ts b/src/tasks/manager/manager.ts index 1a789cb..fe45d61 100644 --- a/src/tasks/manager/manager.ts +++ b/src/tasks/manager/manager.ts @@ -31,8 +31,8 @@ import { } from "./dto.js"; import { TaskStateLogger } from "@tasks/state/logger.js"; import { AgentStateLogger } from "@agents/state/logger.js"; -import { WorkspaceResource } from "@workspaces/workspace-manager.js"; -import { WorkspaceRestorable } from "@workspaces/workspace-restorable.js"; +import { WorkspaceRestorable } from "@workspaces/restore/index.js"; +import { WorkspaceResource } from "@workspaces/manager/index.js"; export type TaskRunRuntime = TaskRun & { intervalId: NodeJS.Timeout | null; diff --git a/src/workspaces/index.ts b/src/workspaces/index.ts index 4eb9d42..80549c0 100644 --- a/src/workspaces/index.ts +++ b/src/workspaces/index.ts @@ -1 +1,7 @@ -export * as workspaces from "./workspace-manager.js"; +import * as workspaceManager from "./manager/index.js"; +import * as restoration from "./restore/index.js"; + +export default { + workspaceManager, + restoration, +} as const; diff --git a/src/workspaces/manager/index.ts b/src/workspaces/manager/index.ts new file mode 100644 index 0000000..4a5b5dd --- /dev/null +++ b/src/workspaces/manager/index.ts @@ -0,0 +1 @@ +export * from "./manager.js"; diff --git a/src/workspaces/workspace-manager.ts b/src/workspaces/manager/manager.ts similarity index 97% rename from src/workspaces/workspace-manager.ts rename to src/workspaces/manager/manager.ts index b0b428d..2a3a37e 100644 --- a/src/workspaces/workspace-manager.ts +++ b/src/workspaces/manager/manager.ts @@ -138,8 +138,7 @@ export class WorkspaceManager extends EventEmitter { input: CreateFileResourceInput | CreateDirectoryResourceInput, ownerId: string, ): WorkspaceResource { - const inputJoinedPath = path.join(this.workspacePath, ...input.path); - const validPath = validatePath(this.workspacePath, inputJoinedPath); + const { inputJoinedPath, validPath } = this.getWorkspacePath(input); const name = basename(validPath); // Check if resource already exists in our tracking @@ -276,4 +275,10 @@ export class WorkspaceManager extends EventEmitter { throw new Error(`Failed to write to file ${resourcePath}: ${error.message}`); } } + + getWorkspacePath(input: CreateFileResourceInput | CreateDirectoryResourceInput) { + const inputJoinedPath = path.join(this.workspacePath, ...input.path); + const validPath = validatePath(this.workspacePath, inputJoinedPath); + return { inputJoinedPath, validPath }; + } } diff --git a/src/workspaces/restore/index.ts b/src/workspaces/restore/index.ts new file mode 100644 index 0000000..e5bd1b2 --- /dev/null +++ b/src/workspaces/restore/index.ts @@ -0,0 +1 @@ +export * from "./restorable.js"; diff --git a/src/workspaces/workspace-restorable.ts b/src/workspaces/restore/restorable.ts similarity index 93% rename from src/workspaces/workspace-restorable.ts rename to src/workspaces/restore/restorable.ts index cff50a3..ec48f61 100644 --- a/src/workspaces/workspace-restorable.ts +++ b/src/workspaces/restore/restorable.ts @@ -1,6 +1,6 @@ +import { WorkspaceManager, WorkspaceResource } from "@workspaces/manager/manager.js"; import { Logger } from "bee-agent-framework"; import { AgentIdValue } from "src/agents/registry/dto.js"; -import { WorkspaceManager, WorkspaceResource } from "./workspace-manager.js"; export abstract class WorkspaceRestorable { protected readonly logger: Logger; From d7fe60c8c930c5eb6b30ca1c51c7d271a23611a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Mon, 24 Feb 2025 13:12:13 +0100 Subject: [PATCH 11/13] fix: paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- package-lock.json | 218 ++++++++++++++++++++++++++- package.json | 27 +++- pnpm-lock.yaml | 148 +++++++++++++++++- src/agents/agent-factory.ts | 4 +- src/agents/agent-id.ts | 12 +- src/agents/operator.ts | 2 +- src/agents/registry/registry.ts | 8 +- src/agents/state/builder.ts | 2 +- src/agents/state/dto.ts | 4 +- src/agents/supervisor.ts | 2 +- src/base/tools-factory.ts | 2 +- src/helpers/llm.ts | 2 +- src/index.ts | 4 - src/main.ts | 1 - src/tasks/manager/dto.ts | 4 +- src/tasks/manager/manager.ts | 8 +- src/tasks/state/builder.ts | 8 +- src/tasks/task-id.ts | 2 +- src/ui/agent-monitor/monitor.ts | 6 +- src/ui/config.ts | 12 +- src/ui/task-monitor/monitor.ts | 8 +- src/workspaces/manager/manager.ts | 2 +- src/workspaces/restore/restorable.ts | 2 +- tsconfig.json | 9 +- 24 files changed, 432 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44e38af..4220e3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bee-supervisor", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bee-supervisor", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@google-cloud/vertexai": "^1.9.2", "@ibm-generative-ai/node-sdk": "^3.2.4", @@ -40,6 +40,7 @@ "pino-pretty": "^11.2.2", "prettier": "^3.3.3", "rimraf": "^5.0.10", + "tsc-alias": "^1.8.10", "tsx": "^4.19.1", "typescript": "~5.5.4", "typescript-eslint": "^8.6.0" @@ -3700,11 +3701,33 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -3900,6 +3923,18 @@ "node": "*" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/blessed": { "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", @@ -4385,6 +4420,18 @@ "node": ">= 0.8" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -5236,6 +5283,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", @@ -5501,6 +5568,18 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", @@ -6147,6 +6226,19 @@ "mustache": "bin/mustache" } }, + "node_modules/mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -6227,6 +6319,15 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -6525,6 +6626,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", @@ -6626,6 +6736,18 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/postcss": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", @@ -6742,6 +6864,15 @@ "node": ">=6" } }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7202,6 +7333,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -7634,6 +7774,80 @@ "typescript": ">=4.2.0" } }, + "node_modules/tsc-alias": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.10.tgz", + "integrity": "sha512-Ibv4KAWfFkFdKJxnWfVtdOmB0Zi1RJVxcbPGiCDsFpCQSsmpWyuzHG3rQyI5YkobWwxFPEyQfu1hdo4qLG2zPw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + } + }, + "node_modules/tsc-alias/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/tsc-alias/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tsc-alias/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/tsconfck": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", diff --git a/package.json b/package.json index aaf59ea..ce3a906 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bee-supervisor", "type": "module", - "version": "0.0.1", + "version": "0.0.5", "private": true, "engines": { "node": ">=18.0.0", @@ -12,6 +12,28 @@ ], "main": "./dist/index.js", "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./agents/*": { + "import": "./dist/agents/*", + "types": "./dist/agents/*.d.ts" + }, + "./tasks/*": { + "import": "./dist/tasks/*", + "types": "./dist/tasks/*.d.ts" + }, + "./workspaces/*": { + "import": "./dist/workspaces/*", + "types": "./dist/workspaces/*.d.ts" + }, + "./ui/*": { + "import": "./dist/ui/*", + "types": "./dist/ui/*.d.ts" + } + }, "homepage": "https://github.com/aleskalfas/bee-supervisor-poc#readme", "repository": { "type": "git", @@ -24,7 +46,7 @@ "start:dev": "tsx --inspect --no-warnings src/main.js", "monitor": "tsx --no-warnings src/ui/main.js", "ts:check": "tsc --noEmit --project tsconfig.json", - "build": "rimraf dist && tsc", + "build": "rimraf dist && tsc && tsc-alias", "lint": "eslint", "lint:fix": "eslint --fix", "format": "prettier --check .", @@ -66,6 +88,7 @@ "pino-pretty": "^11.2.2", "prettier": "^3.3.3", "rimraf": "^5.0.10", + "tsc-alias": "^1.8.10", "tsx": "^4.19.1", "typescript": "~5.5.4", "typescript-eslint": "^8.6.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 455fa63..fdc53fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ importers: "@ibm-generative-ai/node-sdk": specifier: ^3.2.4 version: 3.2.4 + "@modelcontextprotocol/sdk": + specifier: ^1.5.0 + version: 1.5.0 "@opentelemetry/sdk-node": specifier: ^0.57.0 version: 0.57.2(@opentelemetry/api@1.9.0) @@ -21,10 +24,10 @@ importers: version: 0.1.25 bee-agent-framework: specifier: ^0.0.61 - version: 0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0) + version: 0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(@modelcontextprotocol/sdk@1.5.0)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0) bee-observe-connector: specifier: ^0.0.6 - version: 0.0.6(bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0)) + version: 0.0.6(bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(@modelcontextprotocol/sdk@1.5.0)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0)) blessed: specifier: ^0.1.81 version: 0.1.81 @@ -733,6 +736,13 @@ packages: integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==, } + "@modelcontextprotocol/sdk@1.5.0": + resolution: + { + integrity: sha512-IJ+5iVVs8FCumIHxWqpwgkwOzyhtHVKy45s6Ug7Dv0MfRpaYisH8QQ87rIWeWdOzlk8sfhitZ7HCyQZk7d6b8w==, + } + engines: { node: ">=18" } + "@nodelib/fs.scandir@2.1.5": resolution: { @@ -1694,6 +1704,13 @@ packages: integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==, } + bytes@3.1.2: + resolution: + { + integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==, + } + engines: { node: ">= 0.8" } + cac@6.7.14: resolution: { @@ -1842,6 +1859,13 @@ packages: integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, } + content-type@1.0.5: + resolution: + { + integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==, + } + engines: { node: ">= 0.6" } + cross-fetch@4.1.0: resolution: { @@ -1899,6 +1923,13 @@ packages: } engines: { node: ">=0.4.0" } + depd@2.0.0: + resolution: + { + integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, + } + engines: { node: ">= 0.8" } + dot-prop@6.0.1: resolution: { @@ -2149,6 +2180,20 @@ packages: } engines: { node: ">=0.8.x" } + eventsource-parser@3.0.0: + resolution: + { + integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==, + } + engines: { node: ">=18.0.0" } + + eventsource@3.0.5: + resolution: + { + integrity: sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==, + } + engines: { node: ">=18.0.0" } + execa@8.0.1: resolution: { @@ -2525,6 +2570,13 @@ packages: integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==, } + http-errors@2.0.0: + resolution: + { + integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==, + } + engines: { node: ">= 0.8" } + http-status-codes@2.3.0: resolution: { @@ -2605,6 +2657,12 @@ packages: integrity: sha512-d2lTlxKZX7WsYxk9/UPt51nkmZv5tbC75SSw4hfHqZ3LpRAn6ug0oru9xI2X+S78va3aUAze3xl/UqMuwLmJUw==, } + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, + } + is-core-module@2.16.1: resolution: { @@ -3404,6 +3462,13 @@ packages: } engines: { node: ">=14.18.0" } + raw-body@3.0.0: + resolution: + { + integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==, + } + engines: { node: ">= 0.8" } + readable-stream@4.7.0: resolution: { @@ -3572,6 +3637,12 @@ packages: } engines: { node: ">=14.16" } + setprototypeof@1.2.0: + resolution: + { + integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, + } + shebang-command@2.0.0: resolution: { @@ -3645,6 +3716,13 @@ packages: integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, } + statuses@2.0.1: + resolution: + { + integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==, + } + engines: { node: ">= 0.8" } + std-env@3.8.0: resolution: { @@ -3833,6 +3911,13 @@ packages: } engines: { node: ">=8.0" } + toidentifier@1.0.1: + resolution: + { + integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, + } + engines: { node: ">=0.6" } + tr46@0.0.3: resolution: { @@ -3933,6 +4018,13 @@ packages: integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==, } + unpipe@1.0.0: + resolution: + { + integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, + } + engines: { node: ">= 0.8" } + update-browserslist-db@1.1.2: resolution: { @@ -4456,6 +4548,14 @@ snapshots: "@mixmark-io/domino@2.2.0": {} + "@modelcontextprotocol/sdk@1.5.0": + dependencies: + content-type: 1.0.5 + eventsource: 3.0.5 + raw-body: 3.0.0 + zod: 3.24.2 + zod-to-json-schema: 3.24.1(zod@3.24.2) + "@nodelib/fs.scandir@2.1.5": dependencies: "@nodelib/fs.stat": 2.0.5 @@ -5013,7 +5113,7 @@ snapshots: base64-js@1.5.1: {} - bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0): + bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(@modelcontextprotocol/sdk@1.5.0)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0): dependencies: "@ai-zen/node-fetch-event-source": 2.1.4 "@opentelemetry/api": 1.9.0 @@ -5046,6 +5146,7 @@ snapshots: "@google-cloud/vertexai": 1.9.3 "@grpc/grpc-js": 1.12.6 "@grpc/proto-loader": 0.7.13 + "@modelcontextprotocol/sdk": 1.5.0 google-auth-library: 9.15.1 groq-sdk: 0.7.0 ollama: 0.5.13 @@ -5056,9 +5157,9 @@ snapshots: - debug - encoding - bee-observe-connector@0.0.6(bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0)): + bee-observe-connector@0.0.6(bee-agent-framework@0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(@modelcontextprotocol/sdk@1.5.0)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0)): dependencies: - bee-agent-framework: 0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0) + bee-agent-framework: 0.0.61(@google-cloud/vertexai@1.9.3)(@grpc/grpc-js@1.12.6)(@grpc/proto-loader@0.7.13)(@modelcontextprotocol/sdk@1.5.0)(google-auth-library@9.15.1)(groq-sdk@0.7.0)(ollama@0.5.13)(openai-chat-tokens@0.2.8)(openai@4.85.2(zod@3.24.2))(yaml@2.7.0) openapi-fetch: 0.11.3 remeda: 2.20.2 @@ -5093,6 +5194,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bytes@3.1.2: {} + cac@6.7.14: {} call-bind-apply-helpers@1.0.2: @@ -5166,6 +5269,8 @@ snapshots: concat-map@0.0.1: {} + content-type@1.0.5: {} + cross-fetch@4.1.0: dependencies: node-fetch: 2.7.0 @@ -5192,6 +5297,8 @@ snapshots: delayed-stream@1.0.0: {} + depd@2.0.0: {} + dot-prop@6.0.1: dependencies: is-obj: 2.0.0 @@ -5388,6 +5495,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.0: {} + + eventsource@3.0.5: + dependencies: + eventsource-parser: 3.0.0 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -5623,6 +5736,14 @@ snapshots: html-entities@2.5.2: {} + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + http-status-codes@2.3.0: {} https-proxy-agent@7.0.6: @@ -5666,6 +5787,8 @@ snapshots: dependencies: camelcase: 4.1.0 + inherits@2.0.4: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -6110,6 +6233,13 @@ snapshots: ranges-sort@6.0.11: {} + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + readable-stream@4.7.0: dependencies: abort-controller: 3.0.0 @@ -6210,6 +6340,8 @@ snapshots: dependencies: type-fest: 2.19.0 + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -6242,6 +6374,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.1: {} + std-env@3.8.0: {} string-argv@0.3.2: {} @@ -6336,6 +6470,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tr46@0.0.3: {} ts-api-utils@2.0.1(typescript@5.5.4): @@ -6383,6 +6519,8 @@ snapshots: undici-types@5.26.5: {} + unpipe@1.0.0: {} + update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: browserslist: 4.24.4 diff --git a/src/agents/agent-factory.ts b/src/agents/agent-factory.ts index 1b1c15c..2e32d3e 100644 --- a/src/agents/agent-factory.ts +++ b/src/agents/agent-factory.ts @@ -1,10 +1,10 @@ -import { getChatLLM } from "src/helpers/llm.js"; import * as supervisor from "./supervisor.js"; import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; import { UnconstrainedMemory } from "bee-agent-framework/memory/unconstrainedMemory"; import { TokenMemory } from "bee-agent-framework/memory/tokenMemory"; -import { BaseToolsFactory } from "src/base/tools-factory.js"; +import { BaseToolsFactory } from "@/base/tools-factory.js"; import { AgentKindEnum } from "./registry/dto.js"; +import { getChatLLM } from "@/helpers/llm.js"; export interface BaseCreateAgentInput { agentKind: AgentKindEnum; diff --git a/src/agents/agent-id.ts b/src/agents/agent-id.ts index 08ebf50..8e19e2f 100644 --- a/src/agents/agent-id.ts +++ b/src/agents/agent-id.ts @@ -1,18 +1,18 @@ import { EntityKindId, - EntityNumId, - entityToKindString, - entityToTypeIdString, - entityToVersionIdString, - entityToVersionNumIdString, EntityTypeId, EntityVersionId, + EntityNumId, EntityVersionNumId, stringToEntityKind, stringToEntityType, stringToEntityVersion, stringToEntityVersionNum, -} from "src/base/entity-id.js"; + entityToKindString, + entityToTypeIdString, + entityToVersionIdString, + entityToVersionNumIdString, +} from "@/base/entity-id.js"; import { AgentConfigIdValue, AgentConfigVersionValue, diff --git a/src/agents/operator.ts b/src/agents/operator.ts index 4940502..d18ed21 100644 --- a/src/agents/operator.ts +++ b/src/agents/operator.ts @@ -1,6 +1,6 @@ import { ArXivTool } from "bee-agent-framework/tools/arxiv"; import { DuckDuckGoSearchTool } from "bee-agent-framework/tools/search/duckDuckGoSearch"; -import { BaseToolsFactory, ToolFactoryMethod } from "src/base/tools-factory.js"; +import { BaseToolsFactory, ToolFactoryMethod } from "@/base/tools-factory.js"; export class ToolsFactory extends BaseToolsFactory { async getFactoriesMethods(): Promise { diff --git a/src/agents/registry/registry.ts b/src/agents/registry/registry.ts index eeef9e5..ba52f2a 100644 --- a/src/agents/registry/registry.ts +++ b/src/agents/registry/registry.ts @@ -1,6 +1,6 @@ import { clone, isNonNullish } from "remeda"; -import { BaseToolsFactory } from "src/base/tools-factory.js"; -import { updateDeepPartialObject } from "src/utils/objects.js"; +import { BaseToolsFactory } from "@/base/tools-factory.js"; +import { updateDeepPartialObject } from "@/utils/objects.js"; import { agentConfigIdToValue, agentIdToString, @@ -97,10 +97,6 @@ export class AgentRegistry extends WorkspaceRestorable { private poolsToCleanup: string[] = []; private stateLogger: AgentStateLogger; - /** - * Creates a new AgentRegistry instance - * @param callbacks - Callbacks for handling agent lifecycle events and agent type registration - */ constructor({ agentLifecycle, onAgentConfigCreated, diff --git a/src/agents/state/builder.ts b/src/agents/state/builder.ts index a41c794..06bc757 100644 --- a/src/agents/state/builder.ts +++ b/src/agents/state/builder.ts @@ -1,4 +1,4 @@ -import { BaseStateBuilder } from "src/base/state/base-state-builder.js"; +import { BaseStateBuilder } from "@/base/state/base-state-builder.js"; import { agentSomeIdToKindValue, stringToAgentConfig, stringToAgentType } from "../agent-id.js"; import { AgentConfig, diff --git a/src/agents/state/dto.ts b/src/agents/state/dto.ts index 9ecdfa0..e08dd8c 100644 --- a/src/agents/state/dto.ts +++ b/src/agents/state/dto.ts @@ -1,4 +1,4 @@ -import { TaskRunHistoryEntrySchema, TaskRunSchema } from "src/tasks/manager/dto.js"; +import { TaskRunHistoryEntrySchema, TaskRunSchema } from "@/tasks/manager/dto.js"; import { z } from "zod"; import { AgentConfigSchema, @@ -7,7 +7,7 @@ import { AgentConfigIdValueSchema, AgentTypeValueSchema, } from "../registry/dto.js"; -import { DateStringSchema } from "src/base/dto.js"; +import { DateStringSchema } from "@/base/dto.js"; // Base schemas export const AgentEventKindEnum = z.enum([ diff --git a/src/agents/supervisor.ts b/src/agents/supervisor.ts index 04e41eb..04a2f02 100644 --- a/src/agents/supervisor.ts +++ b/src/agents/supervisor.ts @@ -1,7 +1,7 @@ import { TaskManager } from "@tasks/manager/manager.js"; import { TaskManagerTool, TOOL_NAME as taskManagerToolName } from "@tasks/tool.js"; import { WorkspaceManager } from "@workspaces/manager/manager.js"; -import { BaseToolsFactory, ToolFactoryMethod } from "src/base/tools-factory.js"; +import { BaseToolsFactory, ToolFactoryMethod } from "@/base/tools-factory.js"; import { AgentRegistry } from "./registry/index.js"; import { AgentRegistryTool, TOOL_NAME as agentRegistryToolName } from "./registry/tool.js"; diff --git a/src/base/tools-factory.ts b/src/base/tools-factory.ts index cb4d9c8..7af869a 100644 --- a/src/base/tools-factory.ts +++ b/src/base/tools-factory.ts @@ -1,6 +1,6 @@ import { Logger } from "bee-agent-framework"; import { AnyTool } from "bee-agent-framework/tools/base"; -import { AvailableTool } from "src/agents/registry/dto.js"; +import { AvailableTool } from "@/agents/registry/dto.js"; export type ToolFactoryMethod = () => AnyTool; diff --git a/src/helpers/llm.ts b/src/helpers/llm.ts index f5a881b..c84a8d8 100644 --- a/src/helpers/llm.ts +++ b/src/helpers/llm.ts @@ -9,7 +9,7 @@ import { ChatLLM, ChatLLMOutput } from "bee-agent-framework/llms/chat"; import Groq from "groq-sdk"; import { Ollama } from "ollama"; import OpenAI from "openai"; -import { AgentKindEnum } from "src/agents/registry/dto.js"; +import { AgentKindEnum } from "@/agents/registry/dto.js"; import { z } from "zod"; export const Providers = { diff --git a/src/index.ts b/src/index.ts index cb1b13b..652c15a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,10 +8,6 @@ import { WorkspaceManager } from "@workspaces/manager/manager.js"; import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; import { supervisor, operator } from "./agents/index.js"; -export * as agents from "./agents/index.js"; -export * as tasks from "./tasks/index.js"; -export * as workspaces from "./workspaces/index.js"; - export async function createBeeSupervisor(workspace = "default") { // Reset audit logs AgentStateLogger.init(); diff --git a/src/main.ts b/src/main.ts index e19ac63..bf66f71 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,7 +24,6 @@ for await (const { prompt } of reader) { ) .observe((emitter) => { emitter.on("update", (data, meta) => { - // supervisorLogger; reader.write( `${(meta.creator as any).input.meta.name} 🤖 (${data.update.key}) :`, data.update.value, diff --git a/src/tasks/manager/dto.ts b/src/tasks/manager/dto.ts index 6d5cc9d..e5f42a3 100644 --- a/src/tasks/manager/dto.ts +++ b/src/tasks/manager/dto.ts @@ -4,8 +4,8 @@ import { AgentKindEnumSchema, AgentNumValueSchema, AgentTypeValueSchema, -} from "src/agents/registry/dto.js"; -import { DateStringSchema } from "src/base/dto.js"; +} from "@/agents/registry/dto.js"; +import { DateStringSchema } from "@/base/dto.js"; import { z } from "zod"; export const TaskKindEnumSchema = AgentKindEnumSchema.describe( diff --git a/src/tasks/manager/manager.ts b/src/tasks/manager/manager.ts index fe45d61..5e08bbf 100644 --- a/src/tasks/manager/manager.ts +++ b/src/tasks/manager/manager.ts @@ -7,10 +7,10 @@ import { READ_WRITE_ACCESS, ResourcesAccessControl, WRITE_ONLY_ACCESS, -} from "src/access-control/resources-access-control.js"; -import { agentSomeIdToTypeValue } from "src/agents/agent-id.js"; -import { AgentIdValue, AgentKindEnum, AgentTypeValue } from "src/agents/registry/dto.js"; -import { updateDeepPartialObject } from "src/utils/objects.js"; +} from "@/access-control/resources-access-control.js"; +import { agentSomeIdToTypeValue } from "@/agents/agent-id.js"; +import { AgentIdValue, AgentKindEnum, AgentTypeValue } from "@/agents/registry/dto.js"; +import { updateDeepPartialObject } from "@/utils/objects.js"; import { taskConfigIdToValue, taskRunIdToString, taskSomeIdToTypeValue } from "../task-id.js"; import { isTaskRunActiveStatus, diff --git a/src/tasks/state/builder.ts b/src/tasks/state/builder.ts index 72cfeef..723df6a 100644 --- a/src/tasks/state/builder.ts +++ b/src/tasks/state/builder.ts @@ -1,5 +1,5 @@ -import { AgentKindEnum, AgentTypeValue } from "src/agents/registry/dto.js"; -import { BaseStateBuilder } from "src/base/state/base-state-builder.js"; +import { AgentKindEnum, AgentTypeValue } from "@/agents/registry/dto.js"; +import { BaseStateBuilder } from "@/base/state/base-state-builder.js"; import { AgentTypeRegisterEvent, TaskConfigCreateEvent, @@ -13,7 +13,7 @@ import { TaskStateDataType, TaskStateDataTypeSchema, } from "./dto.js"; -import { stringToAgentType } from "src/agents/agent-id.js"; +import { stringToAgentType } from "@/agents/agent-id.js"; import { TaskConfigIdValue, TaskConfig, @@ -25,7 +25,7 @@ import { } from "../manager/dto.js"; import { stringToTaskConfig, stringToTaskType, taskSomeIdToKindValue } from "../task-id.js"; import { clone } from "remeda"; -import { updateDeepPartialObject } from "src/utils/objects.js"; +import { updateDeepPartialObject } from "@/utils/objects.js"; // Define update types as const to ensure type safety export const StateUpdateType = { diff --git a/src/tasks/task-id.ts b/src/tasks/task-id.ts index 4099cec..3d77804 100644 --- a/src/tasks/task-id.ts +++ b/src/tasks/task-id.ts @@ -12,7 +12,7 @@ import { stringToEntityType, stringToEntityVersion, stringToEntityVersionNum, -} from "src/base/entity-id.js"; +} from "@/base/entity-id.js"; import { TaskConfigIdValue, TaskKindEnum, diff --git a/src/ui/agent-monitor/monitor.ts b/src/ui/agent-monitor/monitor.ts index ebca84d..5a815ab 100644 --- a/src/ui/agent-monitor/monitor.ts +++ b/src/ui/agent-monitor/monitor.ts @@ -9,9 +9,9 @@ import { stringToAgent, stringToAgentConfig, stringToAgentKind, -} from "src/agents/agent-id.js"; -import { AgentConfig, AgentKindEnumSchema, AvailableTool } from "src/agents/registry/dto.js"; -import { AgentInfo, AgentStateBuilder, StateUpdateType } from "src/agents/state/builder.js"; +} from "@/agents/agent-id.js"; +import { AgentConfig, AgentKindEnumSchema, AvailableTool } from "@/agents/registry/dto.js"; +import { AgentInfo, AgentStateBuilder, StateUpdateType } from "@/agents/state/builder.js"; import * as st from "../config.js"; import { BaseMonitor, ParentInput, ScreenInput } from "../base/monitor.js"; diff --git a/src/ui/config.ts b/src/ui/config.ts index 5b845dc..bef6c8f 100644 --- a/src/ui/config.ts +++ b/src/ui/config.ts @@ -5,7 +5,7 @@ import { AgentTypeId, stringToAgent, stringToAgentType, -} from "src/agents/agent-id.js"; +} from "@/agents/agent-id.js"; import { AgentConfigPoolStats, AgentKindEnum, @@ -13,8 +13,8 @@ import { AgentKindValue, AgentTypeValue, AvailableTool, -} from "src/agents/registry/dto.js"; -import { AgentInfo } from "src/agents/state/builder.js"; +} from "@/agents/registry/dto.js"; +import { AgentInfo } from "@/agents/state/builder.js"; import { TaskConfigIdValue, TaskConfigPoolStats, @@ -23,16 +23,16 @@ import { TaskKindValue, TaskRunStatusEnum, TaskTypeValue, -} from "src/tasks/manager/dto.js"; +} from "@/tasks/manager/dto.js"; import { stringToTaskRun, stringToTaskType, TaskKindId, TaskRunId, TaskTypeId, -} from "src/tasks/task-id.js"; +} from "@/tasks/task-id.js"; import { UIColors } from "./colors.js"; -import { TaskRunInfo } from "src/tasks/state/builder.js"; +import { TaskRunInfo } from "@/tasks/state/builder.js"; export interface StyleItem { fg?: string; diff --git a/src/ui/task-monitor/monitor.ts b/src/ui/task-monitor/monitor.ts index 7bc4b26..6ecdf0b 100644 --- a/src/ui/task-monitor/monitor.ts +++ b/src/ui/task-monitor/monitor.ts @@ -1,9 +1,9 @@ import blessed from "blessed"; import { join } from "path"; import { clone } from "remeda"; -import { stringToAgent } from "src/agents/agent-id.js"; -import { isTaskRunActiveStatus, TaskConfig, TaskKindEnumSchema } from "src/tasks/manager/dto.js"; -import { StateUpdateType, TaskRunInfo, TaskStateBuilder } from "src/tasks/state/builder.js"; +import { stringToAgent } from "@/agents/agent-id.js"; +import { isTaskRunActiveStatus, TaskConfig, TaskKindEnumSchema } from "@/tasks/manager/dto.js"; +import { StateUpdateType, TaskRunInfo, TaskStateBuilder } from "@/tasks/state/builder.js"; import { stringToTaskConfig, stringToTaskKind, @@ -12,7 +12,7 @@ import { TaskKindId, taskSomeIdToTypeValue, TaskTypeId, -} from "src/tasks/task-id.js"; +} from "@/tasks/task-id.js"; import { BaseMonitor, ParentInput, ScreenInput } from "../base/monitor.js"; import * as st from "../config.js"; diff --git a/src/workspaces/manager/manager.ts b/src/workspaces/manager/manager.ts index 2a3a37e..4f36486 100644 --- a/src/workspaces/manager/manager.ts +++ b/src/workspaces/manager/manager.ts @@ -3,7 +3,7 @@ import EventEmitter from "events"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import path, { basename } from "path"; import { clone } from "remeda"; -import { validatePath } from "src/utils/file.js"; +import { validatePath } from "@/utils/file.js"; export const WORKSPACES_DIR_PATH = ["..", "..", "workspaces"] as const; export const DEFAULT_WORKSPACE_DIR_NAME = "default"; diff --git a/src/workspaces/restore/restorable.ts b/src/workspaces/restore/restorable.ts index ec48f61..ab51675 100644 --- a/src/workspaces/restore/restorable.ts +++ b/src/workspaces/restore/restorable.ts @@ -1,6 +1,6 @@ import { WorkspaceManager, WorkspaceResource } from "@workspaces/manager/manager.js"; import { Logger } from "bee-agent-framework"; -import { AgentIdValue } from "src/agents/registry/dto.js"; +import { AgentIdValue } from "@/agents/registry/dto.js"; export abstract class WorkspaceRestorable { protected readonly logger: Logger; diff --git a/tsconfig.json b/tsconfig.json index f7d843e..e9c9332 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,10 +24,11 @@ "removeComments": false, "forceConsistentCasingInFileNames": true, "paths": { - "@agents/*": ["src/agents/*"], - "@tasks/*": ["src/tasks/*"], - "@workspaces/*": ["src/workspaces/*"], - "@ui/*": ["src/ui/*"] + "@/*": ["./src/*"], + "@agents/*": ["./src/agents/*"], + "@tasks/*": ["./src/tasks/*"], + "@workspaces/*": ["./src/workspaces/*"], + "@ui/*": ["./src/ui/*"] } }, "include": ["src"] From ae89c40eed0b492edb78335f0dac42f4ba2fe941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Tue, 25 Feb 2025 14:53:39 +0100 Subject: [PATCH 12/13] fixup! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- README.md | 8 +- src/agents/agent-factory.ts | 13 +- src/agents/registry/dto.ts | 2 +- src/agents/registry/registry.ts | 252 ++++++----- src/agents/registry/tool.ts | 8 +- src/agents/state/builder.ts | 2 +- src/agents/supervisor.ts | 15 +- src/base/tools-factory.ts | 14 +- src/index.ts | 49 ++- src/main.ts | 2 +- src/tasks/manager/dto.ts | 9 +- src/tasks/manager/manager.ts | 410 ++++++++++++------ src/tasks/state/builder.ts | 3 +- src/ui/agent-monitor/monitor.ts | 2 +- src/ui/config.ts | 5 +- src/utils/objects.ts | 9 +- .../default/configs/agent_registry.jsonl | 4 +- workspaces/default/configs/task_manager.jsonl | 2 +- 18 files changed, 522 insertions(+), 287 deletions(-) diff --git a/README.md b/README.md index 9536ce0..cf7699e 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,13 @@ sequenceDiagram ### Run -`npm start <<< "Hi, can you create poem about each of these topics: bee, hive, queen, sun, flowers?"` +- Monitor + `npm run monitor` + +- Supervisor + `npm run start:dev` + +- Send message like `Hi, can you create poem about each of these topics: bee, hive, queen, sun, flowers?` to the supervisor. ### Live Demo diff --git a/src/agents/agent-factory.ts b/src/agents/agent-factory.ts index 2e32d3e..aa5ae3d 100644 --- a/src/agents/agent-factory.ts +++ b/src/agents/agent-factory.ts @@ -22,7 +22,9 @@ export function createAgent( const llm = getChatLLM(input.agentKind); const generalInstructions = `You are a ${input.agentKind} kind of agent (agentId=${input.agentId}, agentType=${input.agentType}). ${input.instructions}`; switch (input.agentKind) { - case "supervisor": + case "supervisor": { + const tools = toolsFactory.createTools(input.tools); + return new BeeAgent({ meta: { name: input.agentId, @@ -30,14 +32,19 @@ export function createAgent( }, llm, memory: new UnconstrainedMemory(), - tools: toolsFactory.createTools(input.tools), + tools, templates: { system: (template) => template.fork((config) => { - config.defaults.instructions = `${supervisor.SUPERVISOR_INSTRUCTIONS(input.agentKind, input.agentType, input.agentId)}\n\n${generalInstructions}`; + config.defaults.instructions = supervisor.SUPERVISOR_INSTRUCTIONS( + input.agentKind, + input.agentType, + input.agentId, + ); }), }, }); + } case "operator": return new BeeAgent({ meta: { diff --git a/src/agents/registry/dto.ts b/src/agents/registry/dto.ts index a6afaa5..539f275 100644 --- a/src/agents/registry/dto.ts +++ b/src/agents/registry/dto.ts @@ -97,7 +97,7 @@ export type AgentWithInstance = Omit & { * Schema for an available tool. */ export const AvailableToolSchema = z.object({ - name: z.string(), + toolName: z.string(), description: z.string(), }); diff --git a/src/agents/registry/registry.ts b/src/agents/registry/registry.ts index ba52f2a..4af6799 100644 --- a/src/agents/registry/registry.ts +++ b/src/agents/registry/registry.ts @@ -90,6 +90,12 @@ export class AgentRegistry extends WorkspaceRestorable { /** Callbacks for agent lifecycle events */ private lifecycleCallbacks: AgentLifecycleCallbacks; private onAgentConfigCreated: (agentKind: AgentKindEnum, agentType: AgentTypeValue) => void; + private onAgentAvailable: ( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + agentConfigVersion: AgentConfigVersionValue, + availableCount: number, + ) => void; /** Maps of tools factories for use by agents per agent kinds */ private toolsFactory = new Map(); private poolsCleanupJobIntervalId: NodeJS.Timeout | null = null; @@ -100,8 +106,15 @@ export class AgentRegistry extends WorkspaceRestorable { constructor({ agentLifecycle, onAgentConfigCreated, + onAgentAvailable, }: { - onAgentConfigCreated: (agentKind: AgentKindEnum, agentType: string) => void; + onAgentConfigCreated: (agentKind: AgentKindEnum, agentType: AgentTypeValue) => void; + onAgentAvailable: ( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + agentConfigVersion: AgentConfigVersionValue, + availableCount: number, + ) => void; agentLifecycle: AgentLifecycleCallbacks; }) { super(AGENT_REGISTRY_CONFIG_PATH, AGENT_REGISTRY_USER); @@ -109,6 +122,7 @@ export class AgentRegistry extends WorkspaceRestorable { this.stateLogger = AgentStateLogger.getInstance(); this.lifecycleCallbacks = agentLifecycle; this.onAgentConfigCreated = onAgentConfigCreated; + this.onAgentAvailable = onAgentAvailable; // Initialize agent pools for all agent kinds this.agentConfigs = new Map(AgentKindEnumSchema.options.map((kind) => [kind, new Map()])); this.agentPools = new Map(AgentKindEnumSchema.options.map((kind) => [kind, new Map()])); @@ -217,7 +231,7 @@ export class AgentRegistry extends WorkspaceRestorable { const agentConfigTypeMap = this.getAgentConfigMap(agentKind); const agentVersions = agentConfigTypeMap.get(agentType); if (!agentVersions) { - this.logger.error("Agent config type map was not found", { agentKind, agentType }); + this.logger.error({ agentKind, agentType }, "Agent config type map was not found"); throw new Error(`Agent kind '${agentKind}' type '${agentType}' was not found`); } return agentVersions; @@ -226,9 +240,12 @@ export class AgentRegistry extends WorkspaceRestorable { getToolsFactory(agentKind: AgentKindEnum): BaseToolsFactory { const factory = this.toolsFactory.get(agentKind); if (!factory) { - this.logger.error(`There is missing tools factory for the '${agentKind}' agent kind.`, { - agentKind, - }); + this.logger.error( + { + agentKind, + }, + `There is missing tools factory for the '${agentKind}' agent kind.`, + ); throw new Error(`There is missing tools factory for the '${agentKind}' agent kind`); } @@ -240,15 +257,18 @@ export class AgentRegistry extends WorkspaceRestorable { persist = true, ): AgentConfig { const { agentKind, agentType, maxPoolSize } = config; - this.logger.info("Create new agent config", { - agentKind, - agentType, - maxPoolSize, - }); + this.logger.info( + { + agentKind, + agentType, + maxPoolSize, + }, + "Create new agent config", + ); const agentTypesMap = this.getAgentConfigMap(agentKind); if (agentTypesMap.has(agentType)) { - this.logger.error("Agent type already registered", { agentType }); + this.logger.error({ agentType }, "Agent type already registered"); throw new Error(`Agent type '${agentType}' is already registered`); } @@ -256,15 +276,18 @@ export class AgentRegistry extends WorkspaceRestorable { const availableTools = toolsFactory.getAvailableTools(); if (config.tools.filter((it) => !!it.length).length) { const undefinedTools = config.tools.filter( - (tool) => !availableTools.some((at) => at.name === tool), + (tool) => !availableTools.some((at) => at.toolName === tool), ); if (undefinedTools.length) { - this.logger.error(`Tool wasn't found between available tools `, { - availableTools: availableTools.map((at) => at.name), - undefinedTools, - }); + this.logger.error( + { + availableTools: availableTools.map((at) => at.toolName), + undefinedTools, + }, + `Tool wasn't found between available tools `, + ); throw new Error( - `Tools [${undefinedTools.join(",")}] weren't found between available tools [${availableTools.map((at) => at.name).join(",")}]`, + `Tools [${undefinedTools.join(",")}] weren't found between available tools [${availableTools.map((at) => at.toolName).join(",")}]`, ); } } else { @@ -311,15 +334,18 @@ export class AgentRegistry extends WorkspaceRestorable { const toolsFactory = this.getToolsFactory(config.agentKind); const availableTools = toolsFactory.getAvailableTools(); const undefinedTools = update.tools.filter( - (tool) => !availableTools.some((at) => at.name === tool), + (tool) => !availableTools.some((at) => at.toolName === tool), ); if (undefinedTools.length) { - this.logger.error(`Tool wasn't found between available tools `, { - availableTools: availableTools.map((at) => at.name), - undefinedTools, - }); + this.logger.error( + { + availableTools: availableTools.map((at) => at.toolName), + undefinedTools, + }, + `Tool wasn't found between available tools `, + ); throw new Error( - `Tools [${undefinedTools.join(",")}] weren't found between available tools [${availableTools.map((at) => at.name).join(",")}]`, + `Tools [${undefinedTools.join(",")}] weren't found between available tools [${availableTools.map((at) => at.toolName).join(",")}]`, ); } } @@ -354,13 +380,16 @@ export class AgentRegistry extends WorkspaceRestorable { private initializeAgentPool( agentKind: AgentKindEnum, agentType: AgentTypeValue, - version: number, + agentConfigVersion: AgentConfigVersionValue, ) { - this.logger.debug("Initializing agent pool", { - agentKind, - agentType, - version, - }); + this.logger.debug( + { + agentKind, + agentType, + agentConfigVersion, + }, + "Initializing agent pool", + ); const kindPool = this.getAgentKindPoolMap(agentKind); let typePool = kindPool.get(agentType); @@ -368,9 +397,9 @@ export class AgentRegistry extends WorkspaceRestorable { typePool = []; kindPool.set(agentType, typePool); } - typePool.push([version, new Set([])]); + typePool.push([agentConfigVersion, new Set([])]); - this.populatePool(agentKind, agentType, version).catch((error) => { + this.populatePool(agentKind, agentType, agentConfigVersion).catch((error) => { this.logger.error("Failed to populate pool", { agentType, error }); }); } @@ -387,11 +416,11 @@ export class AgentRegistry extends WorkspaceRestorable { agentType: AgentTypeValue, version: number, ): Promise { - this.logger.debug("Populating agent pool", { agentKind, agentType, version }); + this.logger.debug({ agentKind, agentType, version }, "Populating agent pool"); const config = this.getAgentConfig(agentKind, agentType, version); if (config.maxPoolSize <= 0) { - this.logger.trace("Pool population skipped - no pool or size 0", { agentType }); + this.logger.trace({ agentType }, "Pool population skipped - no pool or size 0"); return; } @@ -401,12 +430,15 @@ export class AgentRegistry extends WorkspaceRestorable { const currentPoolSize = pool.size; const needed = config.maxPoolSize - currentPoolSize; - this.logger.debug("Creating agents for pool", { - agentType, - needed, - currentPoolSize, - targetSize: config.maxPoolSize, - }); + this.logger.debug( + { + agentType, + needed, + currentPoolSize, + targetSize: config.maxPoolSize, + }, + "Creating agents for pool", + ); for (let i = 0; i < needed; i++) { await this.createAgent(agentKind, agentType, version); @@ -446,7 +478,7 @@ export class AgentRegistry extends WorkspaceRestorable { if (!this.poolsCleanupJobExecuting) { this.poolsCleanupJobExecuting = true; this.executePoolsCleanup().catch((err) => { - this.logger.error("Execute pool cleanup job error", err); + this.logger.error(err, "Execute pool cleanup job error"); this.stopPoolsCleanupJob(); }); } @@ -475,7 +507,7 @@ export class AgentRegistry extends WorkspaceRestorable { await this.destroyAgent(agent.agentId); destroyed++; } catch (err) { - this.logger.error(`Cleanup error for agent '${agent.agentId}'`, err); + this.logger.error(err, `Cleanup error for agent '${agent.agentId}'`); } } else { this.logger.warn(`Can't cleanup agent '${agent.agentId}' he is in use`); @@ -546,14 +578,17 @@ export class AgentRegistry extends WorkspaceRestorable { agentType: AgentTypeValue, agentConfigVersion?: number, ): AgentConfig { - this.logger.trace("Getting agent type configuration", { - agentKind, - agentType, - agentConfigVersion, - }); + this.logger.trace( + { + agentKind, + agentType, + agentConfigVersion, + }, + "Getting agent type configuration", + ); const configVersions = this.getAgentConfigMap(agentKind).get(agentType); if (!configVersions) { - this.logger.error("Agent config not found", { agentType: agentType }); + this.logger.error({ agentType }, "Agent config not found"); throw new Error(`Agent kind '${agentKind}' type '${agentType}' was not found`); } if (agentConfigVersion != null) { @@ -578,14 +613,17 @@ export class AgentRegistry extends WorkspaceRestorable { agentType: AgentTypeValue, agentConfigVersion: number, ): AgentConfig { - this.logger.trace("Destroying agent configuration", { - agentKind, - agentType, - version: agentConfigVersion, - }); + this.logger.trace( + { + agentKind, + agentType, + version: agentConfigVersion, + }, + "Destroying agent configuration", + ); const configVersions = this.getAgentConfigMap(agentKind).get(agentType); if (!configVersions) { - this.logger.error("Agent config versions was not found", { agentType: agentType }); + this.logger.error({ agentType }, "Agent config versions was not found"); throw new Error( `Agent kind '${agentKind}' type '${agentType}' config versions was not found`, ); @@ -608,12 +646,7 @@ export class AgentRegistry extends WorkspaceRestorable { const destroyedConfig = configVersions.splice(configVersionAt, 1)[0]; const { agentConfigId } = destroyedConfig; - this.logger.info("Agent config destroyed successfully", { - agentConfigId, - agentKind, - agentType, - version: agentConfigVersion, - }); + this.logger.info({ agentConfigId }, "Agent config destroyed successfully"); if (!configVersions.length) { this.getAgentConfigMap(agentKind).delete(agentType); @@ -647,12 +680,12 @@ export class AgentRegistry extends WorkspaceRestorable { agentType: AgentTypeValue, version?: number, ): Promise> { - this.logger.debug("Attempting to acquire agent", { agentKind, agentType, version }); + this.logger.debug({ agentKind, agentType, version }, "Attempting to acquire agent"); const config = this.getAgentConfig(agentKind, agentType, version); const pool = this.getAgentTypeVersionSet(agentKind, agentType, config.agentConfigVersion); if (!pool || config.maxPoolSize === 0) { - this.logger.debug("No pool available, creating new agent", { agentType: agentType }); + this.logger.debug({ agentType }, "No pool available, creating new agent"); return this._acquireAgent( await this.createAgent(agentKind, agentType, config.agentConfigVersion), ); @@ -660,23 +693,25 @@ export class AgentRegistry extends WorkspaceRestorable { // Try to get an available agent from the pool for (const agentId of pool) { - const agent = this.agents.get(agentId); - if (agent && !agent.inUse) { - pool.delete(agentId); - this.logger.debug("Acquired agent from pool", { agentType: agentType, agentId }); + const agent = this.getAgent(agentId); + if (!agent.inUse) { + this.logger.debug({ agentType, agentId }, "Acquired agent from pool"); return this._acquireAgent(agent); } } // No available agents in pool if (pool.size < config.maxPoolSize) { - this.logger.debug("Pool not at capacity, creating new agent", { - agentKind, - agentType, - version: config.agentConfigVersion, - currentSize: pool.size, - maxSize: config.maxPoolSize, - }); + this.logger.debug( + { + agentKind, + agentType, + version: config.agentConfigVersion, + currentSize: pool.size, + maxSize: config.maxPoolSize, + }, + "Pool not at capacity, creating new agent", + ); return this._acquireAgent( await this.createAgent(agentKind, agentType, config.agentConfigVersion), ); @@ -698,7 +733,7 @@ export class AgentRegistry extends WorkspaceRestorable { agent.inUse = true; if (this.lifecycleCallbacks.onAcquire) { - this.logger.trace("Executing onAcquire callback", { agentId }); + this.logger.trace({ agentId }, "Executing onAcquire callback"); await this.lifecycleCallbacks.onAcquire(agentId); } this.stateLogger.logAgentAcquire({ @@ -721,31 +756,31 @@ export class AgentRegistry extends WorkspaceRestorable { * @throws Error if agent is not found */ async releaseAgent(agentId: AgentIdValue): Promise { - this.logger.debug("Attempting to release agent", { agentId }); + this.logger.debug({ agentId }, "Attempting to release agent"); const agent = this.agents.get(agentId); if (!agent) { - this.logger.error("Agent not found for release", { agentId }); + this.logger.error({ agentId }, "Agent not found for release"); throw new Error(`Agent with ID '${agentId}' not found`); } - const { agentKind, agentType, agentConfigVersion: version, maxPoolSize } = agent.config; - const pool = this.getAgentTypeVersionSet(agentKind, agentType, version); + const { agentKind, agentType, agentConfigVersion, maxPoolSize } = agent.config; + const pool = this.getAgentTypeVersionSet(agentKind, agentType, agentConfigVersion); if (!pool || maxPoolSize === 0) { - this.logger.debug("No pool available, destroying agent", { agentId }); + this.logger.debug({ agentId }, "No pool available, destroying agent"); await this.destroyAgent(agentId); return; } if (this.lifecycleCallbacks.onRelease) { - this.logger.trace("Executing onRelease callback", { agentId }); + this.logger.trace({ agentId }, "Executing onRelease callback"); await this.lifecycleCallbacks.onRelease(agentId); } // Return to pool agent.inUse = false; pool.add(agentId); - this.logger.debug("Agent released back to pool", { agentId, agentType: agent.agentType }); + this.logger.debug({ agentId, agentType: agent.agentType }, "Agent released back to pool"); this.stateLogger.logAgentRelease({ agentId: agent.agentId, }); @@ -756,6 +791,8 @@ export class AgentRegistry extends WorkspaceRestorable { poolStats, versions, }); + + this.onAgentAvailable(agentKind, agentType, agentConfigVersion, 1); } private async createAgent( @@ -763,7 +800,7 @@ export class AgentRegistry extends WorkspaceRestorable { agentType: AgentTypeValue, agentConfigVersion: number, ): Promise> { - this.logger.debug("Creating new agent", { agentKind, agentType }); + this.logger.debug({ agentKind, agentType }, "Creating new agent"); const config = this.getAgentConfig(agentKind, agentType, agentConfigVersion); const versionPoolStats = this.getPoolStatsByVersion(agentKind, agentType, agentConfigVersion); const toolsFactory = this.getToolsFactory(agentKind); @@ -795,9 +832,9 @@ export class AgentRegistry extends WorkspaceRestorable { pool.push(poolVersionSetArrayItem); } poolVersionSetArrayItem[1].add(agentId); - this.logger.trace("Added agent to pool", { agentKind, agentType, agentId }); + this.logger.trace({ agentKind, agentType, agentId }, "Added agent to pool"); - this.logger.info("Agent created successfully", { agentId, agentType, agentKind }); + this.logger.info({ agentId, agentType, agentKind }, "Agent created successfully"); this.stateLogger.logAgentCreate({ agentId: agentId, @@ -810,14 +847,15 @@ export class AgentRegistry extends WorkspaceRestorable { poolStats, versions, }); + this.onAgentAvailable(agentKind, agentType, config.agentConfigVersion, 1); return agent; } private async destroyAgent(agentId: AgentIdValue): Promise { - this.logger.debug("Attempting to destroy agent", { agentId }); + this.logger.debug({ agentId }, "Attempting to destroy agent"); const agent = this.agents.get(agentId); if (!agent) { - this.logger.error("Agent not found for destruction", { agentId }); + this.logger.error({ agentId }, "Agent not found for destruction"); throw new Error(`Agent with ID '${agentId}' not found`); } @@ -826,20 +864,23 @@ export class AgentRegistry extends WorkspaceRestorable { await this.lifecycleCallbacks.onDestroy(agent.instance); // Remove from pool if it's in one - const poolSet = this.getAgentTypeVersionSet(agentKind, agentType, agentConfigVersion); - if (poolSet) { - poolSet.delete(agentId); - this.logger.trace("Removed agent from pool", { - agentId, - agentKind, - agentType, - agentConfigVersion, - }); + const pool = this.getAgentTypeVersionSet(agentKind, agentType, agentConfigVersion); + if (pool) { + pool.delete(agentId); + this.logger.trace( + { + agentId, + agentKind, + agentType, + agentConfigVersion, + }, + "Removed agent from pool", + ); } else { throw new Error(`Missing pool`); } - if (!poolSet.size) { + if (!pool.size) { // Remove pool version array set item const poolVersionSetsArray = this.getAgentTypeVersionSetsArray(agentKind, agentType); const poolVersionSet = poolVersionSetsArray.findIndex((it) => it[0] === agentConfigVersion); @@ -847,11 +888,14 @@ export class AgentRegistry extends WorkspaceRestorable { } this.agents.delete(agentId); - this.logger.info("Agent destroyed successfully", { - agentKind, - agentType, - agentConfigVersion, - }); + this.logger.info( + { + agentKind, + agentType, + agentConfigVersion, + }, + "Agent destroyed successfully", + ); this.stateLogger.logAgentDestroy({ agentId, @@ -890,10 +934,10 @@ export class AgentRegistry extends WorkspaceRestorable { } getAgent(agentId: AgentIdValue): Agent { - this.logger.trace("Getting agent by ID", { agentId }); + this.logger.trace({ agentId }, "Getting agent by ID"); const agent = this.agents.get(agentId); if (!agent) { - this.logger.error("Agent not found", { agentId }); + this.logger.error({ agentId }, "Agent not found"); throw new Error(`Agent with ID '${agentId}' not found`); } return agent; @@ -903,7 +947,7 @@ export class AgentRegistry extends WorkspaceRestorable { agentKind: AgentKindEnum, agentType: AgentTypeValue, ): [AgentConfigPoolStats, [number, AgentConfigPoolStats][]] { - this.logger.trace("Getting pool statistics", { agentKind, agentType }); + this.logger.trace({ agentKind, agentType }, "Getting pool statistics"); const pool = this.getAgentTypeVersionSetsArray(agentKind, agentType); if (!pool) { @@ -943,7 +987,7 @@ export class AgentRegistry extends WorkspaceRestorable { } satisfies AgentConfigPoolStats, ); - this.logger.debug("Pool statistics", { agentType: agentType, ...stats }); + this.logger.debug({ agentType, ...stats }, "Pool statistics"); return [stats, versions]; } diff --git a/src/agents/registry/tool.ts b/src/agents/registry/tool.ts index aa32686..db727f4 100644 --- a/src/agents/registry/tool.ts +++ b/src/agents/registry/tool.ts @@ -43,7 +43,9 @@ export interface AgentRegistryToolResult { export const GetAvailableToolsSchema = z .object({ method: z.literal("getAvailableTools"), - agentKind: AgentKindEnumSchema.describe("Kind of agent is mandatory."), + agentKind: AgentKindEnumSchema.default(AgentKindEnumSchema.enum.operator).describe( + "Kind of an agent.", + ), }) .describe( "Get all available tools usable in agents. Use this always before try to assign any tool.", @@ -55,6 +57,8 @@ export const CreateAgentConfigSchema = z agentKind: z.literal(AgentKindEnumSchema.Enum.operator), config: AgentConfigSchema.omit({ agentKind: true, + agentConfigId: true, + agentConfigVersion: true, }), }) .describe("Create a new agent configuration."); @@ -170,7 +174,7 @@ export class AgentRegistryTool extends Tool< let data: AgentRegistryToolResultData; switch (input.method) { case "getAvailableTools": - data = this.registry.getToolsFactory(input.agentKind).getAvailableTools(); + data = this.registry.getToolsFactory(input.agentKind || "operator").getAvailableTools(); break; case "createAgentConfig": data = this.registry.createAgentConfig({ ...input.config, agentKind: input.agentKind }); diff --git a/src/agents/state/builder.ts b/src/agents/state/builder.ts index 06bc757..8415396 100644 --- a/src/agents/state/builder.ts +++ b/src/agents/state/builder.ts @@ -254,7 +254,7 @@ export class AgentStateBuilder extends BaseStateBuilder< Array.from(this.state.availableTools.values()) .flat() .forEach((tool) => { - this.state.allAvailableTools.set(tool.name, tool); + this.state.allAvailableTools.set(tool.toolName, tool); }); } diff --git a/src/agents/supervisor.ts b/src/agents/supervisor.ts index 04a2f02..bb494b1 100644 --- a/src/agents/supervisor.ts +++ b/src/agents/supervisor.ts @@ -19,6 +19,17 @@ export const SUPERVISOR_INSTRUCTIONS = ( * Agent means in this context an umbrella name for agent configuration aka agent config and their instances aka agents. * Agent config is a general definition of particular sort of agent instructed to solve a particular sort of tasks like an agent 'poem_generator' configured to generate poem on some topic (passed by task input). * Agent config is a template for agent instances. Agent is an actual instance of an agent config. + * Agent config instructions is the core part of the agent setting it should be natural language text structured in the following format: + * Instructions should consists of three paragraphs **Context**, **Objective** and **Response format** + * **Context** This paragraph gives background information to help understand the situation. It includes key details, constraints, and relevant knowledge to ensure clarity and consistency. + * **Objective** This paragraph explains the main goal and what needs to be achieved. It sets clear expectations and guidelines to ensure a focused and structured approach. + * **Response format** This paragraph defines the expected structure and style of the response. It ensures consistency and alignment with the intended purpose by specifying format rules, length, organization, or stylistic elements. The response format may include fixed structures (e.g., syllable patterns, rhyme schemes) or flexible guidelines depending on the task requirements. + * Example: + You generate poems on a given topic. The topic will be provided as user input. + + The goal is to produce a well-crafted poem that aligns with the given topic and adheres to any specified constraints. The poem should be engaging, thematically consistent, and exhibit a clear structure. It should creatively explore the subject while demonstrating linguistic elegance, rhythm, and flow. If no constraints are provided, it should default to a balanced poetic form that enhances readability and aesthetic appeal. + + The poem should have 4 stanzas with 4 lines each. The first paragraph of the instructions should provide background information on the topic, the second paragraph should explain the main goal and what needs to be achieved, and the third paragraph should define the expected structure and style of the response. * Agent configs are divided into two main groups by agent kind: * **supervisor** * Agents like you who are able to manage multi-agent platform. @@ -49,7 +60,9 @@ export const SUPERVISOR_INSTRUCTIONS = ( * **Task-agent relation** * Task configs are assigned to the agent configs which secures that if task run is created it is put to the task pool and the platform will care about its assignment to the specific instance of the relevant agent. If the pool of relevant agent has an available agent it auto-assign him to the task run if not the task run will be wait until some will be available. -Your primary mission is to assist the user in achieving their goals, whether through direct conversation or by orchestrating tasks within the system. You must recognize when a task should be created and when it is unnecessary, ensuring that existing tasks and agents are utilized efficiently before initiating new ones. Task execution drives the platform—before creating a task, verify that a similar one does not already exist, and before creating an agent, ensure there is a task that necessitates it. Your role is to plan, coordinate, and optimize task execution, ensuring a seamless and intelligent workflow.`; +Your primary mission is to assist the user in achieving their goals, whether through direct conversation or by orchestrating tasks within the system. You must recognize when a task should be created and when it is unnecessary, ensuring that existing tasks and agents are utilized efficiently before initiating new ones. Task execution drives the platform—before creating a task, verify that a similar one does not already exist, and before creating an agent, ensure there is a task that necessitates it. Your role is to plan, coordinate, and optimize task execution, ensuring a seamless and intelligent workflow. + +REMEMBER: You should not solve tasks directly on your own but through specialized agents and their assigned tasks.`; export class ToolsFactory extends BaseToolsFactory { constructor( diff --git a/src/base/tools-factory.ts b/src/base/tools-factory.ts index 7af869a..d64958f 100644 --- a/src/base/tools-factory.ts +++ b/src/base/tools-factory.ts @@ -8,6 +8,7 @@ export abstract class BaseToolsFactory { protected readonly availableTools = new Map(); protected readonly factories = new Map(); private readonly logger: Logger; + private initialized = false; constructor() { this.logger = Logger.root.child({ name: this.constructor.name }); @@ -18,24 +19,35 @@ export abstract class BaseToolsFactory { for (const factory of methods) { const product = factory(); this.availableTools.set(product.name, { - name: product.name, + toolName: product.name, description: product.description, }); this.factories.set(product.name, factory); } + this.initialized = true; } abstract getFactoriesMethods(): Promise; getAvailableTools(): AvailableTool[] { + this.checkInitialization(); return Array.from(this.availableTools.values()); } getAvailableToolsNames(): string[] { + this.checkInitialization(); return Array.from(this.availableTools.keys()); } + private checkInitialization() { + if (!this.initialized) { + throw new Error(`Uninitialized tools factory`); + } + } + createTools(tools: string[]): AnyTool[] { + this.checkInitialization(); + return tools.map((t) => { const factory = this.factories.get(t); if (!factory) { diff --git a/src/index.ts b/src/index.ts index 652c15a..56118ae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import "dotenv/config"; import { createAgent } from "@agents/agent-factory.js"; import { AgentKindEnumSchema } from "@agents/registry/dto.js"; import { AgentRegistry } from "@agents/registry/registry.js"; @@ -46,12 +47,26 @@ export async function createBeeSupervisor(workspace = "default") { onAgentConfigCreated(agentKind, agentType) { taskManager.registerAgentType(agentKind, agentType); }, + onAgentAvailable(agentKind, agentType, agentConfigVersion, availableCount) { + taskManager.agentAvailable(agentKind, agentType, agentConfigVersion, availableCount); + }, }); const taskManager = new TaskManager( - async (taskRun, taskManager, { onAgentCreate, onAgentComplete, onAgentError }) => { - const agent = await registry.acquireAgent(taskRun.config.agentKind, taskRun.config.agentType); - onAgentCreate(taskRun.taskRunId, agent.agentId, taskManager); + async ( + taskRun, + taskManager, + { onAwaitingAgentAcquired, onAgentAcquired, onAgentComplete, onAgentError }, + ) => { + let agent; + try { + agent = await registry.acquireAgent(taskRun.config.agentKind, taskRun.config.agentType); + } catch { + onAwaitingAgentAcquired(taskRun.taskRunId, taskManager); + return; + } + + onAgentAcquired(taskRun.taskRunId, agent.agentId, taskManager); const { instance } = agent; const prompt = taskRun.taskRunInput; instance @@ -65,20 +80,20 @@ export async function createBeeSupervisor(workspace = "default") { }, }, ) - // .observe((emitter) => { - // emitter.on("update", (data, meta) => { - // reader.write( - // `${(meta.creator as any).input.meta.name} 🤖 (${data.update.key}) :`, - // data.update.value, - // ); - // }); - // emitter.on("error", (data, meta) => { - // reader.write( - // `${(meta.creator as any).input.meta.name} 🤖 (${data.error.name}) :`, - // data.error.message, - // ); - // }); - // }) + // .observe((emitter) => { + // emitter.on("update", (data, meta) => { + // reader.write( + // `${(meta.creator as any).input.meta.name} 🤖 (${data.update.key}) :`, + // data.update.value, + // ); + // }); + // emitter.on("error", (data, meta) => { + // reader.write( + // `${(meta.creator as any).input.meta.name} 🤖 (${data.error.name}) :`, + // data.error.message, + // ); + // }); + // }) .then((resp) => onAgentComplete(resp.result.text, taskRun.taskRunId, agent.agentId, taskManager), ) diff --git a/src/main.ts b/src/main.ts index bf66f71..da836c0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,8 +5,8 @@ import { createBeeSupervisor } from "./index.js"; const args = process.argv.slice(2); const workspace = args[0]; -const supervisorAgent = await createBeeSupervisor(workspace); const reader = createConsoleReader({ fallback: "What is the current weather in Las Vegas?" }); +const supervisorAgent = await createBeeSupervisor(workspace); for await (const { prompt } of reader) { try { const response = await supervisorAgent diff --git a/src/tasks/manager/dto.ts b/src/tasks/manager/dto.ts index e5f42a3..c930c5f 100644 --- a/src/tasks/manager/dto.ts +++ b/src/tasks/manager/dto.ts @@ -122,7 +122,8 @@ export const TaskRunStatusEnumSchema = z.enum([ "CREATED", "SCHEDULED", "EXECUTING", - "WAITING", + "PENDING", + "AWAITING_AGENT", "STOPPED", "FAILED", "COMPLETED", @@ -193,7 +194,8 @@ export const TaskConfigPoolStatsSchema = z completed: z.number(), running: z.number(), - waiting: z.number(), + pending: z.number(), + awaiting_agent: z.number(), stopped: z.number(), failed: z.number(), active: z.number(), @@ -205,7 +207,8 @@ export type TaskConfigPoolStats = z.infer; // Status helpers const ACTIVE_STATUSES = [ TaskRunStatusEnumSchema.enum.SCHEDULED, - TaskRunStatusEnumSchema.enum.WAITING, + TaskRunStatusEnumSchema.enum.PENDING, + TaskRunStatusEnumSchema.enum.AWAITING_AGENT, TaskRunStatusEnumSchema.enum.EXECUTING, ] as const; type ActiveStatus = (typeof ACTIVE_STATUSES)[number]; diff --git a/src/tasks/manager/manager.ts b/src/tasks/manager/manager.ts index 5e08bbf..a9d7c9d 100644 --- a/src/tasks/manager/manager.ts +++ b/src/tasks/manager/manager.ts @@ -1,5 +1,3 @@ -import { FrameworkError } from "bee-agent-framework"; -import { clone, isNonNullish, omit } from "remeda"; import { FULL_ACCESS, READ_EXECUTE_ACCESS, @@ -9,8 +7,19 @@ import { WRITE_ONLY_ACCESS, } from "@/access-control/resources-access-control.js"; import { agentSomeIdToTypeValue } from "@/agents/agent-id.js"; -import { AgentIdValue, AgentKindEnum, AgentTypeValue } from "@/agents/registry/dto.js"; +import { + AgentConfigVersionValue, + AgentIdValue, + AgentKindEnum, + AgentTypeValue, +} from "@/agents/registry/dto.js"; import { updateDeepPartialObject } from "@/utils/objects.js"; +import { AgentStateLogger } from "@agents/state/logger.js"; +import { TaskStateLogger } from "@tasks/state/logger.js"; +import { WorkspaceResource } from "@workspaces/manager/index.js"; +import { WorkspaceRestorable } from "@workspaces/restore/index.js"; +import { FrameworkError } from "bee-agent-framework"; +import { clone, isNonNullish, omit } from "remeda"; import { taskConfigIdToValue, taskRunIdToString, taskSomeIdToTypeValue } from "../task-id.js"; import { isTaskRunActiveStatus, @@ -26,13 +35,10 @@ import { TaskRun, TaskRunHistoryEntry, TaskRunIdValue, + TaskRunStatusEnumSchema, TaskRunTerminalStatusEnum, TaskTypeValue, } from "./dto.js"; -import { TaskStateLogger } from "@tasks/state/logger.js"; -import { AgentStateLogger } from "@agents/state/logger.js"; -import { WorkspaceRestorable } from "@workspaces/restore/index.js"; -import { WorkspaceResource } from "@workspaces/manager/index.js"; export type TaskRunRuntime = TaskRun & { intervalId: NodeJS.Timeout | null; @@ -55,6 +61,10 @@ export class TaskManager extends WorkspaceRestorable { >; private scheduledTasksToStart: { taskRunId: TaskRunIdValue; actingAgentId: AgentIdValue }[] = []; private taskStartIntervalId: NodeJS.Timeout | null = null; + private awaitingTasksForAgents: { + taskRunId: TaskRunIdValue; + agentTypeId: string; + }[] = []; private registeredAgentTypes = new Map(); private ac: ResourcesAccessControl; private stateLogger: TaskStateLogger; @@ -65,7 +75,8 @@ export class TaskManager extends WorkspaceRestorable { taskRun: TaskRun, taskManager: TaskManager, callbacks: { - onAgentCreate: ( + onAwaitingAgentAcquired: (taskRunId: TaskRunIdValue, taskManage: TaskManager) => void; + onAgentAcquired: ( taskRunId: TaskRunIdValue, agentId: AgentIdValue, taskManage: TaskManager, @@ -101,7 +112,7 @@ export class TaskManager extends WorkspaceRestorable { this.options = { errorHandler: (error: Error, taskRunId: TaskRunIdValue) => { - this.logger.error("Task error occurred", { taskRunId, error }); + this.logger.error({ taskRunId, error }, "Task error occurred"); }, occupancyTimeoutMs: 30 * 60 * 1000, adminIds: [], @@ -117,7 +128,7 @@ export class TaskManager extends WorkspaceRestorable { try { await this.processNextStartTask(); // Your async function } catch (err) { - this.logger.error("Process next start task error", err); + this.logger.error(err, "Process next start task error"); } }, 100); // Runs every 100ms (0.1 second) } @@ -197,12 +208,12 @@ export class TaskManager extends WorkspaceRestorable { actingAgentId: AgentIdValue, permissions = READ_ONLY_ACCESS, ): TaskRunRuntime { - this.logger.trace("Getting task run by ID", { taskRunId }); + this.logger.trace({ taskRunId }, "Getting task run by ID"); this.ac.checkPermission(taskRunId, actingAgentId, permissions); const taskRun = this.taskRuns.get(taskRunId); if (!taskRun) { - this.logger.error("Task run not found", { taskRunId }); + this.logger.error({ taskRunId }, "Task run not found"); throw new Error(`Task run with ID '${taskRunId}' not found`); } return taskRun; @@ -213,7 +224,7 @@ export class TaskManager extends WorkspaceRestorable { taskType: TaskTypeValue, actingAgentId: AgentIdValue, ): [TaskConfigPoolStats, [number, TaskConfigPoolStats][]] { - this.logger.trace("Getting pool statistics", { taskKind, taskType, actingAgentId }); + this.logger.trace({ taskKind, taskType, actingAgentId }, "Getting pool statistics"); this.ac.checkPermission(TASK_MANAGER_RESOURCE, actingAgentId, READ_ONLY_ACCESS); @@ -224,7 +235,8 @@ export class TaskManager extends WorkspaceRestorable { poolSize: 0, created: 0, running: 0, - waiting: 0, + pending: 0, + awaiting_agent: 0, stopped: 0, failed: 0, completed: 0, @@ -259,7 +271,8 @@ export class TaskManager extends WorkspaceRestorable { running: taskRuns.filter((t) => t.status === "EXECUTING").length, failed: taskRuns.filter((t) => t.status === "FAILED").length, stopped: taskRuns.filter((t) => t.status === "STOPPED").length, - waiting: taskRuns.filter((t) => t.status === "WAITING").length, + pending: taskRuns.filter((t) => t.status === "PENDING").length, + awaiting_agent: taskRuns.filter((t) => t.status === "AWAITING_AGENT").length, created: taskRuns.filter((t) => t.status === "CREATED").length, total: taskRuns.length, } satisfies TaskConfigPoolStats; @@ -276,7 +289,8 @@ export class TaskManager extends WorkspaceRestorable { running: curr.running + prev.running, failed: curr.failed + prev.failed, stopped: curr.stopped + prev.stopped, - waiting: curr.waiting + prev.waiting, + pending: curr.pending + prev.pending, + awaiting_agent: curr.awaiting_agent + prev.awaiting_agent, created: curr.created + prev.created, total: curr.total + prev.total, } satisfies TaskConfigPoolStats; @@ -290,13 +304,14 @@ export class TaskManager extends WorkspaceRestorable { running: 0, failed: 0, stopped: 0, - waiting: 0, + pending: 0, + awaiting_agent: 0, created: 0, total: 0, } satisfies TaskConfigPoolStats, ); - this.logger.debug("Pool statistics", { taskType: taskType, ...stats }); + this.logger.trace({ taskType: taskType, ...stats }, "Pool statistics"); return [stats, versions]; } @@ -318,7 +333,8 @@ export class TaskManager extends WorkspaceRestorable { running: 0, failed: 0, stopped: 0, - waiting: 0, + pending: 0, + awaiting_agent: 0, created: 0, total: 0, } satisfies TaskConfigPoolStats; @@ -372,16 +388,19 @@ export class TaskManager extends WorkspaceRestorable { persist = true, ): TaskConfig { const { taskKind, taskType, maxRepeats: maxRuns } = config; - this.logger.info("Create new task config", { - taskKind, - taskType, - maxRuns, - }); + this.logger.info( + { + taskKind, + taskType, + maxRuns, + }, + "Create new task config", + ); this.ac.checkPermission(TASK_MANAGER_RESOURCE, actingAgentId, WRITE_ONLY_ACCESS); const taskTypesMap = this.getTaskConfigMap(taskKind); if (taskTypesMap.has(taskType)) { - this.logger.error("Task type already registered", { taskType }); + this.logger.error({ taskType }, "Task type already registered"); throw new Error(`Task type '${taskType}' is already registered`); } @@ -424,11 +443,14 @@ export class TaskManager extends WorkspaceRestorable { } private initializeTaskPool(taskKind: TaskKindEnum, taskType: TaskTypeValue, version: number) { - this.logger.debug("Initializing task pool", { - taskKind, - taskType, - version, - }); + this.logger.debug( + { + taskKind, + taskType, + version, + }, + "Initializing task pool", + ); const kindPool = this.getTaskKindPoolMap(taskKind); let typePool = kindPool.get(taskType); @@ -451,7 +473,7 @@ export class TaskManager extends WorkspaceRestorable { const taskConfigTypeMap = this.getTaskConfigMap(taskKind); const taskConfigVersions = taskConfigTypeMap.get(taskType); if (!taskConfigVersions) { - this.logger.error("Task config type map was not found", { taskKind, taskType }); + this.logger.error({ taskKind, taskType }, "Task config type map was not found"); throw new Error(`Task kind '${taskKind}' type '${taskType}' was not found`); } return taskConfigVersions; @@ -466,7 +488,7 @@ export class TaskManager extends WorkspaceRestorable { ): TaskConfig { const configVersions = this.getTaskConfigMap(taskKind).get(taskType); if (!configVersions) { - this.logger.error("Task config not found", { taskType }); + this.logger.error({ taskKind, taskType }, "Task config not found"); throw new Error(`Task kind '${taskKind}' type '${taskType}' was not found`); } @@ -474,6 +496,10 @@ export class TaskManager extends WorkspaceRestorable { if (taskConfigVersion != null) { const configVersion = configVersions.find((c) => c.taskConfigVersion === taskConfigVersion); if (!configVersion) { + this.logger.error( + { taskKind, taskType, taskConfigVersion }, + "Task config version not found", + ); throw new Error( `Task kind '${taskKind}' type '${taskType}' version '${taskConfigVersion}' was not found`, ); @@ -483,6 +509,10 @@ export class TaskManager extends WorkspaceRestorable { const lastConfigVersion = configVersions.at(-1); if (lastConfigVersion == null) { + this.logger.error( + { taskKind, taskType, taskConfigVersion }, + "Task config last version was not found", + ); throw new Error(`Task kind '${taskKind}' type '${taskType}' last version was not found`); } result = lastConfigVersion; @@ -555,11 +585,11 @@ export class TaskManager extends WorkspaceRestorable { taskType: TaskTypeValue, actingAgentId: AgentIdValue, ): void { - this.logger.trace("Destroying agent configuration", { taskKind, taskType }); + this.logger.trace({ taskKind, taskType, actingAgentId }, "Destroying agent configuration"); const configVersions = this.getTaskConfigMap(taskKind).get(taskType); if (!configVersions) { - this.logger.error("Task config versions was not found", { taskKind, taskType }); + this.logger.error({ taskKind, taskType }, "Task config versions was not found"); throw new Error(`Task kind '${taskKind}' type '${taskType}' config versions was not found`); } @@ -573,17 +603,24 @@ export class TaskManager extends WorkspaceRestorable { actingAgentId, ); if (stats.active) { + this.logger.error( + { taskKind, taskType, stats }, + "Task config can't be destroyed while it is still has active runs", + ); throw new Error( `Task config kind '${taskKind}' type '${taskType}' version '${taskConfigVersion}' can't be destroyed while it is still has active runs.`, ); } configVersions.splice(index, 1)[0]; - this.logger.info("Task config destroyed successfully", { - taskConfigId, - taskKind, - taskType, - taskConfigVersion, - }); + this.logger.info( + { + taskConfigId, + taskKind, + taskType, + taskConfigVersion, + }, + "Task config destroyed successfully", + ); this.stateLogger.logTaskConfigDestroy({ taskConfigId, @@ -620,11 +657,14 @@ export class TaskManager extends WorkspaceRestorable { taskRunInput: string, actingAgentId: AgentIdValue, ): TaskRun { - this.logger.debug("Creating new task run", { - taskKind, - taskType, - actingAgentId, - }); + this.logger.debug( + { + taskKind, + taskType, + actingAgentId, + }, + "Creating new task run", + ); const config = this.getTaskConfig( taskKind, @@ -694,7 +734,7 @@ export class TaskManager extends WorkspaceRestorable { pool.push(poolVersionSetArrayItem); } poolVersionSetArrayItem[1].add(taskRunId); - this.logger.trace("Added task to pool", { taskKind, taskType, taskConfigVersion, taskRunId }); + this.logger.trace({ taskKind, taskType, taskConfigVersion, taskRunId }, "Added task to pool"); const [poolStats, versions] = this.getPoolStats(taskKind, taskType, actingAgentId); this.stateLogger.logPoolChange({ @@ -715,12 +755,12 @@ export class TaskManager extends WorkspaceRestorable { * Only owners and admins can start/stop tasks. */ scheduleStartTaskRun(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): void { - this.logger.info("Schedule task run start", { taskRunId, actingAgentId }); + this.logger.info({ taskRunId, actingAgentId }, "Schedule task run start"); this.ac.checkPermission(taskRunId, actingAgentId, FULL_ACCESS); const taskRun = this.taskRuns.get(taskRunId); if (!taskRun) { - this.logger.error("Task run not found", { taskRunId }); + this.logger.error({ taskRunId }, "Task run not found"); throw new Error(`Task run ${taskRunId} not found`); } @@ -734,7 +774,7 @@ export class TaskManager extends WorkspaceRestorable { ); if (versionPoolStats.active >= versionPoolStats.poolSize) { - this.logger.trace("Task pool population is full", { taskKind, taskType, taskConfigVersion }); + this.logger.trace({ taskKind, taskType, taskConfigVersion }, "Task pool population is full"); return; } @@ -750,21 +790,16 @@ export class TaskManager extends WorkspaceRestorable { } const { taskRunId, actingAgentId } = this.scheduledTasksToStart.shift()!; - this.logger.info("Starting scheduled task run", { taskRunId, actingAgentId }); + this.logger.info({ taskRunId, actingAgentId }, "Starting scheduled task run"); const taskRun = this.getTaskRun(taskRunId, actingAgentId, FULL_ACCESS); - if (!taskRun) { - this.logger.error("Task not found", { taskRunId }); - throw new Error(`Task ${taskRunId} not found`); - } - if (taskRun.status === "EXECUTING") { - this.logger.warn("Task is already executing", { taskRunId }); + this.logger.warn({ taskRunId }, "Task is already executing"); throw new Error(`Task ${taskRunId} is already executing`); } if (taskRun.config.runImmediately || !taskRun.config.intervalMs) { - this.logger.debug("Executing task immediately", { taskRunId }); + this.logger.debug({ taskRunId }, "Executing task immediately"); await this.executeTask(taskRunId, actingAgentId); } @@ -772,42 +807,45 @@ export class TaskManager extends WorkspaceRestorable { taskRun.config.intervalMs && (taskRun.config.maxRepeats == null || taskRun.completedRuns < taskRun.config.maxRepeats) ) { - this.logger.debug("Setting up task interval", { - taskRunId, - intervalMs: taskRun.config.intervalMs, - }); + this.logger.debug( + { + taskRunId, + intervalMs: taskRun.config.intervalMs, + }, + "Setting up task interval", + ); const self = this; taskRun.intervalId = setInterval(async () => { self.executeTask(taskRunId, actingAgentId); }, taskRun.config.intervalMs); this._updateTaskRun(taskRunId, taskRun, { - status: "WAITING", + status: "PENDING", nextRunAt: new Date(Date.now() + taskRun.config.intervalMs), }); } - this.logger.info("Task started successfully", { taskRunId }); + this.logger.info({ taskRunId }, "Task started successfully"); } stopTaskRun(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue, isCompleted = false): void { - this.logger.info("Stopping task", { taskRunId, actingAgentId }); + this.logger.info({ taskRunId, actingAgentId }, "Stopping task"); this.ac.checkPermission(taskRunId, actingAgentId, READ_WRITE_ACCESS); const taskRun = this.getTaskRun(taskRunId, actingAgentId); if (taskRun.status === "STOPPED") { - this.logger.debug("Task already stopped", { taskRunId }); + this.logger.debug({ taskRunId }, "Task already stopped"); return; } if (taskRun.intervalId) { - this.logger.debug("Clearing task interval", { taskRunId }); + this.logger.debug({ taskRunId }, "Clearing task interval"); clearInterval(taskRun.intervalId); taskRun.intervalId = null; } if (taskRun.isOccupied) { - this.logger.debug("Releasing task occupancy before stop", { taskRunId }); + this.logger.debug({ taskRunId }, "Releasing task occupancy before stop"); this.releaseTaskRunOccupancy(taskRunId, actingAgentId); } @@ -815,26 +853,26 @@ export class TaskManager extends WorkspaceRestorable { status: isCompleted ? "COMPLETED" : "STOPPED", nextRunAt: undefined, }); - this.logger.info("Task stopped successfully", { taskRunId }); + this.logger.info({ taskRunId }, "Task stopped successfully"); } destroyTaskRun(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): void { - this.logger.info("Attempting to destroy task run", { taskRunId, actingAgentId }); + this.logger.info({ taskRunId, actingAgentId }, "Attempting to destroy task run"); this.ac.checkPermission(taskRunId, actingAgentId, WRITE_ONLY_ACCESS); const taskRun = this.taskRuns.get(taskRunId); if (!taskRun) { - this.logger.error("Task run not found for destruction", { taskRunId }); + this.logger.error({ taskRunId }, "Task run not found for destruction"); throw new Error(`Task run with ID '${taskRunId}' not found`); } if (taskRun.status === "EXECUTING") { - this.logger.debug("Stopping executing task before removal", { taskRunId }); + this.logger.debug({ taskRunId }, "Stopping executing task before removal"); this.stopTaskRun(taskRunId, actingAgentId); } if (taskRun.isOccupied) { - this.logger.debug("Releasing task occupancy before removal", { taskRunId }); + this.logger.debug({ taskRunId }, "Releasing task occupancy before removal"); this.releaseTaskRunOccupancy(taskRunId, actingAgentId); } @@ -846,12 +884,15 @@ export class TaskManager extends WorkspaceRestorable { const poolSet = this.getTaskTypeVersionSet(taskKind, taskType, taskConfigVersion); if (poolSet) { poolSet.delete(taskRunId); - this.logger.trace("Removed task run from pool", { - taskRunId, - taskKind, - taskType, - taskConfigVersion, - }); + this.logger.trace( + { + taskRunId, + taskKind, + taskType, + taskConfigVersion, + }, + "Removed task run from pool", + ); } else { throw new Error(`Missing pool`); } @@ -864,11 +905,14 @@ export class TaskManager extends WorkspaceRestorable { } this.taskRuns.delete(taskRunId); - this.logger.info("Task run destroyed successfully", { - taskKind, - taskType, - taskConfigVersion, - }); + this.logger.info( + { + taskKind, + taskType, + taskConfigVersion, + }, + "Task run destroyed successfully", + ); this.stateLogger.logTaskRunDestroy({ taskRunId, @@ -882,20 +926,46 @@ export class TaskManager extends WorkspaceRestorable { }); } + private setTaskRunAwaitingAgent(taskRunId: TaskRunIdValue) { + this.logger.info({ taskRunId }, "Setting task run awaiting agent"); + + const taskRun = this.getTaskRun(taskRunId, TASK_MANAGER_USER); + if (taskRun.status === TaskRunStatusEnumSchema.enum.AWAITING_AGENT) { + this.logger.debug({ taskRunId }, "It's already awaiting - skip"); + return; + } + + this._updateTaskRun(taskRunId, taskRun, { + status: TaskRunStatusEnumSchema.enum.AWAITING_AGENT, + }); + + this.awaitingTasksForAgents.push({ + taskRunId, + agentTypeId: agentSomeIdToTypeValue({ + agentKind: taskRun.config.agentKind, + agentType: taskRun.config.agentType, + agentConfigVersion: taskRun.config.agentVersion, + }), + }); + } + /** * Sets task as occupied. * Only authorized agents can occupy tasks. */ private setTaskRunOccupied(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): boolean { - this.logger.info("Setting task run as occupied", { taskRunId, agentId: actingAgentId }); + this.logger.info({ taskRunId, agentId: actingAgentId }, "Setting task run as occupied"); this.ac.createPermissions(taskRunId, actingAgentId, FULL_ACCESS, TASK_MANAGER_USER); const taskRun = this.getTaskRun(taskRunId, actingAgentId); if (taskRun.isOccupied) { - this.logger.debug("Task not available for occupancy", { - taskRunId, - exists: !!taskRun, - }); + this.logger.debug( + { + taskRunId, + exists: !!taskRun, + }, + "Task not available for occupancy", + ); return false; } @@ -906,24 +976,29 @@ export class TaskManager extends WorkspaceRestorable { currentAgentId: actingAgentId, }); + const assignment = clone(omit(taskRun, ["intervalId"])); + this.agentStateLogger.logTaskAssigned({ agentId: actingAgentId, - assignment: clone(omit(taskRun, ["intervalId"])), + assignment, assignmentId: taskRunId, assignedSince: occupiedSince, }); if (this.options.occupancyTimeoutMs) { - this.logger.debug("Setting occupancy timeout", { - taskRunId, - timeoutMs: this.options.occupancyTimeoutMs, - }); + this.logger.debug( + { + taskRunId, + timeoutMs: this.options.occupancyTimeoutMs, + }, + "Setting occupancy timeout", + ); setTimeout(() => { this.releaseTaskRunOccupancy(taskRunId, actingAgentId); }, this.options.occupancyTimeoutMs); } - this.logger.info("Task occupied successfully", { taskRunId, agentId: actingAgentId }); + this.logger.info({ taskRunId, actingAgentId }, "Task occupied successfully"); return true; } @@ -932,11 +1007,11 @@ export class TaskManager extends WorkspaceRestorable { * Only the current agent or owners can release occupancy. */ private releaseTaskRunOccupancy(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): boolean { - this.logger.info("Releasing task occupancy", { taskRunId, agentId: actingAgentId }); + this.logger.info({ taskRunId, agentId: actingAgentId }, "Releasing task occupancy"); const taskRun = this.getTaskRun(taskRunId, actingAgentId); if (!taskRun || !taskRun.isOccupied) { - this.logger.debug("Task not available for release", { taskRunId, exists: !!taskRun }); + this.logger.debug({ taskRunId, exists: !!taskRun }, "Task not available for release"); return false; } @@ -955,10 +1030,37 @@ export class TaskManager extends WorkspaceRestorable { unassignedAt, }); - this.logger.info("Task occupancy released successfully", { taskRunId }); + this.logger.info({ taskRunId }, "Task occupancy released successfully"); return true; } + agentAvailable( + agentKind: AgentKindEnum, + agentType: AgentTypeValue, + agentConfigVersion: AgentConfigVersionValue, + availableCount: number, + ) { + if (!this.agentAvailable.length) { + return; + } + let rest = availableCount; + let index = 0; + const awaitingTasks = clone(this.awaitingTasksForAgents); + for (const { taskRunId, agentTypeId } of awaitingTasks) { + if (agentTypeId !== agentSomeIdToTypeValue({ agentKind, agentType, agentConfigVersion })) { + index++; + continue; + } + this.awaitingTasksForAgents.splice(index, 1); + this.scheduleStartTaskRun(taskRunId, TASK_MANAGER_USER); + rest--; + if (!rest) { + return; + } + index++; + } + } + /** * Update task status */ @@ -967,7 +1069,7 @@ export class TaskManager extends WorkspaceRestorable { update: Partial>, actingAgentId: AgentIdValue, ) { - this.logger.info("Updating task status", { taskRunId, actingAgentId, update }); + this.logger.info({ taskRunId, actingAgentId, update }, "Updating task status"); const status = this.getTaskRun(taskRunId, actingAgentId, WRITE_ONLY_ACCESS); return this._updateTaskRun(taskRunId, status, update); } @@ -996,13 +1098,13 @@ export class TaskManager extends WorkspaceRestorable { * Gets all task runs visible to the agent. */ getAllTaskRuns(agentId: AgentIdValue): TaskRun[] { - this.logger.info("Getting all task runs", { agentId }); + this.logger.info({ agentId }, "Getting all task runs"); const taskRuns = Array.from(this.taskRuns.values()).filter((taskRun) => this.ac.hasPermission(taskRun.taskRunId, agentId, READ_ONLY_ACCESS), ); - this.logger.debug("Retrieved task runs", { agentId, count: taskRuns.length }); + this.logger.debug({ agentId, count: taskRuns.length }, "Retrieved task runs"); return taskRuns; } @@ -1010,12 +1112,12 @@ export class TaskManager extends WorkspaceRestorable { * Checks if a task is currently occupied. */ isTaskRunOccupied(taskRunId: TaskRunIdValue, agentId: AgentIdValue): boolean { - this.logger.debug("Checking task occupancy", { taskRunId, agentId }); + this.logger.debug({ taskRunId, agentId }, "Checking task occupancy"); this.ac.checkPermission(taskRunId, agentId, READ_ONLY_ACCESS); const task = this.taskRuns.get(taskRunId); if (!task) { - this.logger.error("Task not found", { taskRunId }); + this.logger.error({ taskRunId }, "Task not found"); throw new Error(`Undefined taskRunId: ${taskRunId}`); } @@ -1027,45 +1129,55 @@ export class TaskManager extends WorkspaceRestorable { * @private */ private async executeTask(taskRunId: TaskRunIdValue, actingAgentId: AgentIdValue): Promise { - this.logger.info("Executing task", { taskRunId, agentId: actingAgentId }); + this.logger.info({ taskRunId, agentId: actingAgentId }, "Executing task"); this.ac.checkPermission(taskRunId, actingAgentId, READ_EXECUTE_ACCESS); const taskRun = this.getTaskRun(taskRunId, actingAgentId); const retryAttempt = taskRun.currentRetryAttempt; if (retryAttempt > 0) { - this.logger.debug("Retry attempt", { retryAttempt, maxRetries: taskRun.config.maxRetries }); + this.logger.debug({ retryAttempt, maxRetries: taskRun.config.maxRetries }, "Retry attempt"); if (!!taskRun.config.maxRetries && retryAttempt >= taskRun.config.maxRetries) { - this.logger.warn("Last retry attempt", { taskRunId }); + this.logger.warn({ taskRunId }, "Last retry attempt"); } } if (taskRun.status === "COMPLETED" || taskRun.isOccupied) { - this.logger.debug("Skipping task execution", { - taskRunId, - reason: taskRun.status === "COMPLETED" ? "completed" : "occupied", - }); + this.logger.debug( + { + taskRunId, + reason: taskRun.status === "COMPLETED" ? "completed" : "occupied", + }, + "Skipping task execution", + ); return; } - - const startTime = Date.now(); + const startAt = new Date(); + const startTime = startAt.getTime(); this._updateTaskRun(taskRunId, taskRun, { status: "EXECUTING", - lastRunAt: new Date(), + startRunAt: startAt, + lastRunAt: startAt, nextRunAt: taskRun.config.maxRepeats != null && taskRun.completedRuns < taskRun.config.maxRepeats ? new Date(Date.now() + taskRun.config.intervalMs) : undefined, }); - this.logger.debug("Executing task callback", { - taskRunId, - lastRunAt: taskRun.lastRunAt, - nextRunAt: taskRun.nextRunAt, - }); + this.logger.debug( + { + taskRunId, + lastRunAt: taskRun.lastRunAt, + nextRunAt: taskRun.nextRunAt, + }, + "Executing task callback", + ); await this.onTaskStart(taskRun, this, { - onAgentCreate(taskRunId, agentId, taskManager) { + onAwaitingAgentAcquired(taskRunId, taskManager) { + taskManager.setTaskRunAwaitingAgent(taskRunId); + }, + onAgentAcquired(taskRunId, agentId, taskManager) { taskManager.setTaskRunOccupied(taskRunId, agentId); }, onAgentComplete(output, taskRunId, agentId, taskManager) { @@ -1087,11 +1199,14 @@ export class TaskManager extends WorkspaceRestorable { executionTimeMs: Date.now() - startTime, }); - taskManager.logger.debug("Task executed successfully", { - taskRunId, - completedRuns: taskRun.completedRuns, - maxRuns: taskRun.config.maxRepeats, - }); + taskManager.logger.debug( + { + taskRunId, + completedRuns: taskRun.completedRuns, + maxRuns: taskRun.config.maxRepeats, + }, + "Task executed successfully", + ); // Check if we've reached maxRuns if ( @@ -1099,11 +1214,14 @@ export class TaskManager extends WorkspaceRestorable { (taskRun.config.maxRepeats && taskRun.completedRuns >= taskRun.config.maxRepeats) ) { taskManager.stopTaskRun(taskRunId, agentId); - taskManager.logger.info("Task reached maximum runs and has been stopped", { - taskRunId, - completedRuns: taskRun.completedRuns, - maxRuns: taskRun.config.maxRepeats, - }); + taskManager.logger.info( + { + taskRunId, + completedRuns: taskRun.completedRuns, + maxRuns: taskRun.config.maxRepeats, + }, + "Task reached maximum runs and has been stopped", + ); } else { taskManager.releaseTaskRunOccupancy(taskRunId, agentId); } @@ -1136,21 +1254,24 @@ export class TaskManager extends WorkspaceRestorable { executionTimeMs: Date.now() - startTime, }); - taskManager.logger.error(`Task execution failed ${error}`, { - taskRunId, - runNumber: taskRun.completedRuns, - maxRuns: taskRun.config.maxRepeats, - retryAttempt, - maxRepeats: taskRun.config.maxRepeats, - errorCount: taskRun.errorCount, - error, - }); + taskManager.logger.error( + { + taskRunId, + runNumber: taskRun.completedRuns, + maxRuns: taskRun.config.maxRepeats, + retryAttempt, + maxRepeats: taskRun.config.maxRepeats, + errorCount: taskRun.errorCount, + error, + }, + `Task execution failed ${error}`, + ); if (taskManager.options.errorHandler) { taskManager.options.errorHandler(err as Error, taskRunId); } - taskManager.logger.debug("Releasing task occupancy before removal", { taskRunId }); + taskManager.logger.debug({ taskRunId }, "Releasing task occupancy before removal"); taskManager.releaseTaskRunOccupancy(taskRunId, agentId); if (taskRun.config.maxRepeats) { if (retryAttempt >= taskRun.config.maxRepeats) { @@ -1204,11 +1325,14 @@ export class TaskManager extends WorkspaceRestorable { status?: TaskRunTerminalStatusEnum; } = {}, ): TaskRunHistoryEntry[] { - this.logger.trace("Getting task history", { - taskRunId, - agentId: actingAgentId, - options, - }); + this.logger.trace( + { + taskRunId, + agentId: actingAgentId, + options, + }, + "Getting task history", + ); this.ac.checkPermission(taskRunId, actingAgentId, READ_ONLY_ACCESS); const taskRun = this.getTaskRun(taskRunId, actingAgentId); diff --git a/src/tasks/state/builder.ts b/src/tasks/state/builder.ts index 723df6a..7bd2895 100644 --- a/src/tasks/state/builder.ts +++ b/src/tasks/state/builder.ts @@ -246,7 +246,8 @@ export class TaskStateBuilder extends BaseStateBuilder this.stateBuilder.getAllTools().get(t) ?? { - name: "Undefined", + toolName: "Undefined", description: "Lorem ipsum....", }, ); diff --git a/src/ui/config.ts b/src/ui/config.ts index bef6c8f..9991e8e 100644 --- a/src/ui/config.ts +++ b/src/ui/config.ts @@ -110,7 +110,8 @@ export const UIConfig = { CREATED: { fg: UIColors.yellow.yellow, icon: "◆" }, // Diamond EXECUTING: { fg: UIColors.green.green, icon: "▶" }, // Play triangle SCHEDULED: { fg: UIColors.blue.cyan, icon: "◇" }, // Hollow diamond - WAITING: { fg: UIColors.blue.cyan, icon: "◆" }, // Hollow diamond + PENDING: { fg: UIColors.blue.cyan, icon: "◆" }, // Hollow diamond + AWAITING_AGENT: { fg: UIColors.blue.steel_blue, icon: "◇" }, // Hollow diamond FAILED: { fg: UIColors.red.red, icon: "×" }, // Square COMPLETED: { fg: UIColors.blue.blue, icon: "●" }, // Circle STOPPED: { fg: UIColors.gray.gray, icon: "◼" }, // Filled square @@ -243,7 +244,7 @@ export function applyAgentIdStyle(agentId: AgentId | AgentTypeId) { export function applyToolsStyle(tools: AvailableTool[]) { return tools .map((t) => [ - applyToolNameStyle(t.name), + applyToolNameStyle(t.toolName), applyStyle(t.description, UIConfig.labels.description), "", ]) diff --git a/src/utils/objects.ts b/src/utils/objects.ts index 191fe94..60ad98b 100644 --- a/src/utils/objects.ts +++ b/src/utils/objects.ts @@ -14,12 +14,17 @@ export function updateDeepPartialObject(original: T, update: D for (const key of keys) { const updateValue = update[key]; + // Special case for Date objects + if (updateValue instanceof Date) { + original[key] = updateValue as T[keyof T]; + } // Handle nested objects recursively - if ( + else if ( updateValue !== null && typeof updateValue === "object" && !Array.isArray(updateValue) && - typeof original[key] === "object" + typeof original[key] === "object" && + !(original[key] instanceof Date) // Don't treat Date as a regular object ) { original[key] = { ...(original[key] as object), diff --git a/workspaces/default/configs/agent_registry.jsonl b/workspaces/default/configs/agent_registry.jsonl index 95e5d47..d9b80d0 100644 --- a/workspaces/default/configs/agent_registry.jsonl +++ b/workspaces/default/configs/agent_registry.jsonl @@ -1,2 +1,2 @@ -{"agentConfigId":"supervisor:boss:1","agentConfigVersion":1,"agentKind":"supervisor","agentType":"boss","instructions":"","description":"The boss supervisor agent that control whole app.","tools":["agent_registry","task_runner"],"maxPoolSize":1,"autoPopulatePool":false} -{"agentConfigId":"operator:poem_generator:1","agentConfigVersion":1,"agentType":"poem_generator","instructions":"Generate a poem based on the input topic.","description":"An agent that generates poems on a given topic.","tools":[],"maxPoolSize":5,"autoPopulatePool":true,"agentKind":"operator"} \ No newline at end of file +{"autoPopulatePool":false,"agentKind":"supervisor","agentType":"boss","instructions":"","tools":["agent_registry","task_runner"],"description":"The boss supervisor agent that control whole app.","maxPoolSize":1,"agentConfigId":"supervisor:boss:1","agentConfigVersion":1} +{"agentType":"poem_generator","instructions":"You generate poems on a given topic. The topic will be provided as user input. \n\nThe goal is to produce a well-crafted poem that aligns with the given topic and adheres to any specified constraints. The poem should be engaging, thematically consistent, and exhibit a clear structure. It should creatively explore the subject while demonstrating linguistic elegance, rhythm, and flow. If no constraints are provided, it should default to a balanced poetic form that enhances readability and aesthetic appeal.\n\nThe poem should have 4 stanzas with 4 lines each. The first paragraph of the instructions should provide background information on the topic, the second paragraph should explain the main goal and what needs to be achieved, and the third paragraph should define the expected structure and style of the response.","description":"This agent generates poems on given topics.","tools":[],"maxPoolSize":1,"autoPopulatePool":true,"agentKind":"operator","agentConfigId":"operator:poem_generator:1","agentConfigVersion":1} \ No newline at end of file diff --git a/workspaces/default/configs/task_manager.jsonl b/workspaces/default/configs/task_manager.jsonl index 1985d56..8393bd0 100644 --- a/workspaces/default/configs/task_manager.jsonl +++ b/workspaces/default/configs/task_manager.jsonl @@ -1 +1 @@ -{"ownerId":"supervisor:boss[1]:1","taskConfig":{"taskKind":"operator","taskType":"poem_generation","taskConfigInput":"{}","description":"Generate a poem based on the input topic.","intervalMs":0,"runImmediately":false,"agentKind":"operator","agentType":"poem_generator","agentNum":1,"agentVersion":1,"concurrencyMode":"PARALLEL","maxRetries":3,"retryDelayMs":1000,"taskConfigId":"operator:poem_generation:1","ownerAgentId":"supervisor:boss[1]:1","taskConfigVersion":1}} \ No newline at end of file +{"ownerId":"supervisor:boss[1]:1","taskConfig":{"taskKind":"operator","taskType":"poem_generation","taskConfigInput":"{}","description":"This task generates a poem on a given topic.","intervalMs":0,"runImmediately":false,"maxRetries":3,"retryDelayMs":10000,"agentKind":"operator","agentType":"poem_generator","agentNum":1,"agentVersion":1,"concurrencyMode":"PARALLEL","maxRepeats":null,"taskConfigId":"operator:poem_generation:1","ownerAgentId":"supervisor:boss[1]:1","taskConfigVersion":1}} \ No newline at end of file From cd6199bf529b1f01aa03d31911117019b24fe525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kalfas?= Date: Tue, 25 Feb 2025 15:28:37 +0100 Subject: [PATCH 13/13] chore: update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aleš Kalfas --- README.md | 36 +++++++++++++++++++++++++++++------- media/monitor.png | Bin 0 -> 246643 bytes media/supervisor.png | Bin 0 -> 25374 bytes 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 media/monitor.png create mode 100644 media/supervisor.png diff --git a/README.md b/README.md index cf7699e..7acc236 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,34 @@ A proof-of-concept implementation of a multi-agent task management system that demonstrates hierarchical agent coordination and task based on [BeeAI Framework](https://i-am-bee.github.io/bee-agent-framework#/) ❤️. +## 🚀 Running the Project + +1. Start the Supervisor Agent and send him a message + +```sh +npm run start:dev <<< "Can you generate a poem about each of these topics: bee, hive, queen, sun, flowers?" +``` + +![Supervisor terminal](./media/supervisor.png) + +2. **Observe the process** in the Monitor UI. + +```sh +npm run monitor +``` + +![Monitor GUI](./media/monitor.png) + +> [!CAUTION] > **Outdated Documentation** +> The README and video are outdated and do not reflect the current state of the project, which has evolved significantly. +> Some of the latest features not covered include: +> +> - **Task Configurations** – Work similarly to agent configurations. +> - **CLI UI Monitor** – Provides real-time monitoring for agents and tasks. +> - **Workspaces** – Enables restoration of previous work. + +--- + ## Features - **Agent Registry**: Manages different types of agents and maintains agent pools @@ -152,13 +180,7 @@ sequenceDiagram ### Run -- Monitor - `npm run monitor` - -- Supervisor - `npm run start:dev` - -- Send message like `Hi, can you create poem about each of these topics: bee, hive, queen, sun, flowers?` to the supervisor. +`npm start <<< "Hi, can you create poem about each of these topics: bee, hive, queen, sun, flowers?"` ### Live Demo diff --git a/media/monitor.png b/media/monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..72cd9acf7dc71068f1f20554ab6545c3ee55931b GIT binary patch literal 246643 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nQ;vF{Wo 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
)LNX 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 E2Uq=24*&oF literal 0 HcmV?d00001 diff --git a/media/supervisor.png b/media/supervisor.png new file mode 100644 index 0000000000000000000000000000000000000000..cf94fee68faeea114305b65c9f3d2d9192b98290 GIT binary patch literal 25374 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 tEk_p8e;2VMLjq*g|L!gS@4bZr880+tcK~0!M