mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
refactor: convert ManagedAgent to discriminated unions and remove AgentSnapshot
Replace ManagedAgent with discriminated union keyed on lifecycle state to make impossible states unrepresentable. Remove AgentSnapshot type and toSnapshot method entirely - consumers now receive ManagedAgent directly. - Define ManagedAgent as discriminated union with 5 lifecycle states - Add comprehensive immutability protection (deep clone + freeze) - Update getAgent/listAgents to return immutable ManagedAgent views - Remove AgentSnapshot type and toSnapshot method - Update event emissions to use ManagedAgent Breaking change: All consumers must now use ManagedAgent or projection functions instead of AgentSnapshot. Tasks 2 & 3 of 8 in agent architecture refactor. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -33,27 +33,8 @@ export const AGENT_LIFECYCLE_STATUSES = [
|
||||
export type AgentLifecycleStatus =
|
||||
(typeof AGENT_LIFECYCLE_STATUSES)[number];
|
||||
|
||||
export type AgentSnapshot = {
|
||||
id: string;
|
||||
provider: AgentProvider;
|
||||
cwd: string;
|
||||
model: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
lastUserMessageAt: Date | null;
|
||||
status: AgentLifecycleStatus;
|
||||
sessionId: string | null;
|
||||
capabilities: AgentCapabilityFlags;
|
||||
currentModeId: string | null;
|
||||
availableModes: AgentMode[];
|
||||
pendingPermissions: AgentPermissionRequest[];
|
||||
persistence: AgentPersistenceHandle | null;
|
||||
lastUsage?: AgentUsage;
|
||||
lastError?: string;
|
||||
};
|
||||
|
||||
export type AgentManagerEvent =
|
||||
| { type: "agent_state"; agent: AgentSnapshot }
|
||||
| { type: "agent_state"; agent: ManagedAgent }
|
||||
| { type: "agent_stream"; agentId: string; event: AgentStreamEvent };
|
||||
|
||||
export type AgentSubscriber = (event: AgentManagerEvent) => void;
|
||||
@@ -83,29 +64,70 @@ export type WaitForAgentResult = {
|
||||
lastMessage: string | null;
|
||||
};
|
||||
|
||||
type ManagedAgent = {
|
||||
type ManagedAgentBase = {
|
||||
id: string;
|
||||
provider: AgentProvider;
|
||||
cwd: string;
|
||||
session: AgentSession;
|
||||
sessionId: string | null;
|
||||
capabilities: AgentCapabilityFlags;
|
||||
config: AgentSessionConfig;
|
||||
status: AgentLifecycleStatus;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
availableModes: AgentMode[];
|
||||
currentModeId: string | null;
|
||||
pendingPermissions: Map<string, AgentPermissionRequest>;
|
||||
pendingRun: AsyncGenerator<AgentStreamEvent> | null;
|
||||
timeline: AgentTimelineItem[];
|
||||
persistence: AgentPersistenceHandle | null;
|
||||
lastUsage?: AgentUsage;
|
||||
lastError?: string;
|
||||
historyPrimed: boolean;
|
||||
lastUserMessageAt: Date | null;
|
||||
sessionId: string | null;
|
||||
lastUsage?: AgentUsage;
|
||||
lastError?: string;
|
||||
};
|
||||
|
||||
type ManagedAgentWithSession = ManagedAgentBase & {
|
||||
session: AgentSession;
|
||||
};
|
||||
|
||||
type ManagedAgentInitializing = ManagedAgentWithSession & {
|
||||
lifecycle: "initializing";
|
||||
pendingRun: null;
|
||||
};
|
||||
|
||||
type ManagedAgentIdle = ManagedAgentWithSession & {
|
||||
lifecycle: "idle";
|
||||
pendingRun: null;
|
||||
};
|
||||
|
||||
type ManagedAgentRunning = ManagedAgentWithSession & {
|
||||
lifecycle: "running";
|
||||
pendingRun: AsyncGenerator<AgentStreamEvent>;
|
||||
};
|
||||
|
||||
type ManagedAgentError = ManagedAgentWithSession & {
|
||||
lifecycle: "error";
|
||||
pendingRun: null;
|
||||
lastError: string;
|
||||
};
|
||||
|
||||
type ManagedAgentClosed = ManagedAgentBase & {
|
||||
lifecycle: "closed";
|
||||
session: null;
|
||||
pendingRun: null;
|
||||
};
|
||||
|
||||
export type ManagedAgent =
|
||||
| ManagedAgentInitializing
|
||||
| ManagedAgentIdle
|
||||
| ManagedAgentRunning
|
||||
| ManagedAgentError
|
||||
| ManagedAgentClosed;
|
||||
|
||||
type ActiveManagedAgent =
|
||||
| ManagedAgentInitializing
|
||||
| ManagedAgentIdle
|
||||
| ManagedAgentRunning
|
||||
| ManagedAgentError;
|
||||
|
||||
type SubscriptionRecord = {
|
||||
callback: AgentSubscriber;
|
||||
agentId: string | null;
|
||||
@@ -117,6 +139,23 @@ const BUSY_STATUSES: AgentLifecycleStatus[] = [
|
||||
"running",
|
||||
];
|
||||
|
||||
const READ_ONLY_AGENT_ERROR_MESSAGE =
|
||||
"ManagedAgent views returned by AgentManager are read-only";
|
||||
|
||||
class ReadonlyPendingPermissionsMap<K, V> extends Map<K, V> {
|
||||
override set(): this {
|
||||
throw new Error(READ_ONLY_AGENT_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
override delete(): boolean {
|
||||
throw new Error(READ_ONLY_AGENT_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
override clear(): void {
|
||||
throw new Error(READ_ONLY_AGENT_ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
function isAgentBusy(status: AgentLifecycleStatus): boolean {
|
||||
return BUSY_STATUSES.includes(status);
|
||||
}
|
||||
@@ -137,7 +176,7 @@ function createAbortError(
|
||||
|
||||
export class AgentManager {
|
||||
private readonly clients = new Map<AgentProvider, AgentClient>();
|
||||
private readonly agents = new Map<string, ManagedAgent>();
|
||||
private readonly agents = new Map<string, ActiveManagedAgent>();
|
||||
private readonly subscribers = new Set<SubscriptionRecord>();
|
||||
private readonly maxTimelineItems: number;
|
||||
private readonly idFactory: () => string;
|
||||
@@ -170,11 +209,17 @@ export class AgentManager {
|
||||
if (record.agentId) {
|
||||
const agent = this.agents.get(record.agentId);
|
||||
if (agent) {
|
||||
callback({ type: "agent_state", agent: this.toSnapshot(agent) });
|
||||
callback({
|
||||
type: "agent_state",
|
||||
agent: this.createAgentView(agent),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (const agent of this.agents.values()) {
|
||||
callback({ type: "agent_state", agent: this.toSnapshot(agent) });
|
||||
callback({
|
||||
type: "agent_state",
|
||||
agent: this.createAgentView(agent),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,9 +229,9 @@ export class AgentManager {
|
||||
};
|
||||
}
|
||||
|
||||
listAgents(): AgentSnapshot[] {
|
||||
listAgents(): ManagedAgent[] {
|
||||
return Array.from(this.agents.values()).map((agent) =>
|
||||
this.toSnapshot(agent)
|
||||
this.createAgentView(agent)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -228,9 +273,9 @@ export class AgentManager {
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
getAgent(id: string): AgentSnapshot | null {
|
||||
getAgent(id: string): ManagedAgent | null {
|
||||
const agent = this.agents.get(id);
|
||||
return agent ? this.toSnapshot(agent) : null;
|
||||
return agent ? this.createAgentView(agent) : null;
|
||||
}
|
||||
|
||||
getTimeline(id: string): AgentTimelineItem[] {
|
||||
@@ -241,7 +286,7 @@ export class AgentManager {
|
||||
async createAgent(
|
||||
config: AgentSessionConfig,
|
||||
agentId?: string
|
||||
): Promise<AgentSnapshot> {
|
||||
): Promise<ManagedAgent> {
|
||||
const normalizedConfig = await this.normalizeConfig(config);
|
||||
const client = this.requireClient(normalizedConfig.provider);
|
||||
const session = await client.createSession(normalizedConfig);
|
||||
@@ -256,7 +301,7 @@ export class AgentManager {
|
||||
handle: AgentPersistenceHandle,
|
||||
overrides?: Partial<AgentSessionConfig>,
|
||||
agentId?: string
|
||||
): Promise<AgentSnapshot> {
|
||||
): Promise<ManagedAgent> {
|
||||
const metadata = (handle.metadata ?? {}) as Partial<AgentSessionConfig>;
|
||||
const mergedConfig = {
|
||||
...metadata,
|
||||
@@ -277,7 +322,7 @@ export class AgentManager {
|
||||
);
|
||||
}
|
||||
|
||||
async refreshAgentFromPersistence(agentId: string): Promise<AgentSnapshot> {
|
||||
async refreshAgentFromPersistence(agentId: string): Promise<ManagedAgent> {
|
||||
const existing = this.requireAgent(agentId);
|
||||
const handle = existing.persistence;
|
||||
if (!handle) {
|
||||
@@ -311,9 +356,16 @@ export class AgentManager {
|
||||
async closeAgent(agentId: string): Promise<void> {
|
||||
const agent = this.requireAgent(agentId);
|
||||
this.agents.delete(agentId);
|
||||
agent.status = "closed";
|
||||
await agent.session.close();
|
||||
this.emitState(agent);
|
||||
const session = agent.session;
|
||||
const closedAgent: ManagedAgent = {
|
||||
...agent,
|
||||
lifecycle: "closed",
|
||||
session: null,
|
||||
pendingRun: null,
|
||||
sessionId: agent.sessionId,
|
||||
};
|
||||
await session.close();
|
||||
this.emitState(closedAgent);
|
||||
}
|
||||
|
||||
async setAgentMode(agentId: string, modeId: string): Promise<void> {
|
||||
@@ -382,16 +434,14 @@ export class AgentManager {
|
||||
prompt: AgentPromptInput,
|
||||
options?: AgentRunOptions
|
||||
): AsyncGenerator<AgentStreamEvent> {
|
||||
const agent = this.requireAgent(agentId);
|
||||
if (agent.status === "closed") {
|
||||
throw new Error(`Agent ${agentId} is closed`);
|
||||
}
|
||||
if (agent.pendingRun) {
|
||||
const existingAgent = this.requireAgent(agentId);
|
||||
if (existingAgent.pendingRun) {
|
||||
throw new Error(`Agent ${agentId} already has an active run`);
|
||||
}
|
||||
|
||||
const agent = existingAgent as ActiveManagedAgent;
|
||||
const iterator = agent.session.stream(prompt, options);
|
||||
agent.status = "running";
|
||||
agent.lifecycle = "running";
|
||||
agent.pendingRun = iterator;
|
||||
agent.lastError = undefined;
|
||||
this.emitState(agent);
|
||||
@@ -410,11 +460,12 @@ export class AgentManager {
|
||||
return;
|
||||
}
|
||||
|
||||
agent.pendingRun = null;
|
||||
agent.status = error ? "error" : "idle";
|
||||
agent.lastError = error;
|
||||
agent.persistence = agent.session.describePersistence();
|
||||
this.emitState(agent);
|
||||
const mutableAgent = agent as ActiveManagedAgent;
|
||||
mutableAgent.pendingRun = null;
|
||||
mutableAgent.lifecycle = error ? "error" : "idle";
|
||||
mutableAgent.lastError = error;
|
||||
mutableAgent.persistence = mutableAgent.session.describePersistence();
|
||||
this.emitState(mutableAgent);
|
||||
};
|
||||
|
||||
const self = this;
|
||||
@@ -491,6 +542,11 @@ export class AgentManager {
|
||||
return Array.from(agent.pendingPermissions.values());
|
||||
}
|
||||
|
||||
private peekPendingPermission(agent: ManagedAgent): AgentPermissionRequest | null {
|
||||
const iterator = agent.pendingPermissions.values().next();
|
||||
return iterator.done ? null : iterator.value;
|
||||
}
|
||||
|
||||
async primeAgentHistory(agentId: string): Promise<void> {
|
||||
const agent = this.requireAgent(agentId);
|
||||
await this.primeHistory(agent);
|
||||
@@ -532,18 +588,18 @@ export class AgentManager {
|
||||
}
|
||||
|
||||
|
||||
const immediatePermission = snapshot.pendingPermissions[0] ?? null;
|
||||
const immediatePermission = this.peekPendingPermission(snapshot);
|
||||
if (immediatePermission) {
|
||||
return {
|
||||
status: snapshot.status,
|
||||
status: snapshot.lifecycle,
|
||||
permission: immediatePermission,
|
||||
lastMessage: this.getLastAssistantMessage(agentId)
|
||||
};
|
||||
}
|
||||
|
||||
if (!isAgentBusy(snapshot.status)) {
|
||||
if (!isAgentBusy(snapshot.lifecycle)) {
|
||||
return {
|
||||
status: snapshot.status,
|
||||
status: snapshot.lifecycle,
|
||||
permission: null,
|
||||
lastMessage: this.getLastAssistantMessage(agentId)
|
||||
};
|
||||
@@ -562,7 +618,7 @@ export class AgentManager {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentStatus: AgentLifecycleStatus = snapshot.status;
|
||||
let currentStatus: AgentLifecycleStatus = snapshot.lifecycle;
|
||||
|
||||
// Bug #3 Fix: Declare unsubscribe and abortHandler upfront so cleanup can reference them
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
@@ -615,13 +671,13 @@ export class AgentManager {
|
||||
(event) => {
|
||||
// Bug #2 Fix: Only handle agent_state events, remove redundant agent_stream handling
|
||||
if (event.type === "agent_state") {
|
||||
currentStatus = event.agent.status;
|
||||
const pending = event.agent.pendingPermissions[0] ?? null;
|
||||
currentStatus = event.agent.lifecycle;
|
||||
const pending = this.peekPendingPermission(event.agent);
|
||||
if (pending) {
|
||||
finish(pending);
|
||||
return;
|
||||
}
|
||||
if (!isAgentBusy(event.agent.status)) {
|
||||
if (!isAgentBusy(event.agent.lifecycle)) {
|
||||
finish(null);
|
||||
}
|
||||
}
|
||||
@@ -635,12 +691,12 @@ export class AgentManager {
|
||||
session: AgentSession,
|
||||
config: AgentSessionConfig,
|
||||
agentId: string
|
||||
): Promise<AgentSnapshot> {
|
||||
): Promise<ManagedAgent> {
|
||||
if (this.agents.has(agentId)) {
|
||||
throw new Error(`Agent with id ${agentId} already exists`);
|
||||
}
|
||||
|
||||
const managed: ManagedAgent = {
|
||||
const managed = {
|
||||
id: agentId,
|
||||
provider: config.provider,
|
||||
cwd: config.cwd,
|
||||
@@ -648,7 +704,7 @@ export class AgentManager {
|
||||
sessionId: session.id,
|
||||
capabilities: session.capabilities,
|
||||
config,
|
||||
status: "initializing",
|
||||
lifecycle: "initializing",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
availableModes: [],
|
||||
@@ -659,18 +715,18 @@ export class AgentManager {
|
||||
persistence: session.describePersistence(),
|
||||
historyPrimed: false,
|
||||
lastUserMessageAt: null,
|
||||
};
|
||||
} as ActiveManagedAgent;
|
||||
|
||||
this.agents.set(agentId, managed);
|
||||
this.emitState(managed);
|
||||
|
||||
await this.refreshSessionState(managed);
|
||||
managed.status = "idle";
|
||||
managed.lifecycle = "idle";
|
||||
this.emitState(managed);
|
||||
return this.toSnapshot(managed);
|
||||
return this.createAgentView(managed);
|
||||
}
|
||||
|
||||
private async refreshSessionState(agent: ManagedAgent): Promise<void> {
|
||||
private async refreshSessionState(agent: ActiveManagedAgent): Promise<void> {
|
||||
try {
|
||||
const modes = await agent.session.getAvailableModes();
|
||||
agent.availableModes = modes;
|
||||
@@ -694,7 +750,7 @@ export class AgentManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async primeHistory(agent: ManagedAgent): Promise<void> {
|
||||
private async primeHistory(agent: ActiveManagedAgent): Promise<void> {
|
||||
if (agent.historyPrimed) {
|
||||
return;
|
||||
}
|
||||
@@ -709,7 +765,7 @@ export class AgentManager {
|
||||
}
|
||||
|
||||
private handleStreamEvent(
|
||||
agent: ManagedAgent,
|
||||
agent: ActiveManagedAgent,
|
||||
event: AgentStreamEvent,
|
||||
options?: { fromHistory?: boolean }
|
||||
): void {
|
||||
@@ -763,7 +819,10 @@ export class AgentManager {
|
||||
}
|
||||
|
||||
private emitState(agent: ManagedAgent): void {
|
||||
this.dispatch({ type: "agent_state", agent: this.toSnapshot(agent) });
|
||||
this.dispatch({
|
||||
type: "agent_state",
|
||||
agent: this.createAgentView(agent),
|
||||
});
|
||||
}
|
||||
|
||||
private dispatchStream(agentId: string, event: AgentStreamEvent): void {
|
||||
@@ -790,25 +849,156 @@ export class AgentManager {
|
||||
}
|
||||
}
|
||||
|
||||
private toSnapshot(agent: ManagedAgent): AgentSnapshot {
|
||||
private createAgentView(agent: ManagedAgent): ManagedAgent {
|
||||
const clone = this.cloneAgent(agent);
|
||||
clone.pendingPermissions = this.createReadonlyPendingPermissionsMap(
|
||||
clone.pendingPermissions
|
||||
);
|
||||
|
||||
this.deepFreezeValue(clone.availableModes);
|
||||
this.deepFreezeValue(clone.timeline);
|
||||
this.deepFreezeValue(clone.capabilities);
|
||||
this.deepFreezeValue(clone.config);
|
||||
if (clone.persistence) {
|
||||
this.deepFreezeValue(clone.persistence);
|
||||
}
|
||||
if (clone.lastUsage) {
|
||||
this.deepFreezeValue(clone.lastUsage);
|
||||
}
|
||||
if (clone.lastUserMessageAt) {
|
||||
Object.freeze(clone.lastUserMessageAt);
|
||||
}
|
||||
Object.freeze(clone.createdAt);
|
||||
Object.freeze(clone.updatedAt);
|
||||
|
||||
return Object.freeze(clone) as ManagedAgent;
|
||||
}
|
||||
|
||||
private cloneAgent(agent: ManagedAgent): ManagedAgent {
|
||||
return {
|
||||
id: agent.id,
|
||||
provider: agent.provider,
|
||||
cwd: agent.cwd,
|
||||
model: agent.config.model ?? null,
|
||||
createdAt: agent.createdAt,
|
||||
updatedAt: agent.updatedAt,
|
||||
lastUserMessageAt: agent.lastUserMessageAt,
|
||||
status: agent.status,
|
||||
sessionId: agent.sessionId,
|
||||
capabilities: agent.capabilities,
|
||||
currentModeId: agent.currentModeId,
|
||||
availableModes: agent.availableModes,
|
||||
pendingPermissions: Array.from(agent.pendingPermissions.values()),
|
||||
persistence: agent.persistence,
|
||||
lastUsage: agent.lastUsage,
|
||||
lastError: agent.lastError,
|
||||
};
|
||||
...agent,
|
||||
createdAt: new Date(agent.createdAt.getTime()),
|
||||
updatedAt: new Date(agent.updatedAt.getTime()),
|
||||
lastUserMessageAt: agent.lastUserMessageAt
|
||||
? new Date(agent.lastUserMessageAt.getTime())
|
||||
: null,
|
||||
pendingPermissions: new Map(
|
||||
Array.from(agent.pendingPermissions.entries()).map(
|
||||
([key, value]) => [key, this.cloneValue(value)]
|
||||
)
|
||||
),
|
||||
availableModes: this.cloneValue(agent.availableModes),
|
||||
timeline: this.cloneValue(agent.timeline),
|
||||
capabilities: this.cloneValue(agent.capabilities),
|
||||
config: this.cloneValue(agent.config),
|
||||
persistence: agent.persistence
|
||||
? this.cloneValue(agent.persistence)
|
||||
: agent.persistence,
|
||||
lastUsage: agent.lastUsage ? this.cloneValue(agent.lastUsage) : undefined,
|
||||
} as ManagedAgent;
|
||||
}
|
||||
|
||||
private createReadonlyPendingPermissionsMap(
|
||||
source: Map<string, AgentPermissionRequest>
|
||||
): Map<string, AgentPermissionRequest> {
|
||||
const readonlyMap = new ReadonlyPendingPermissionsMap(source);
|
||||
for (const request of readonlyMap.values()) {
|
||||
this.deepFreezeValue(request);
|
||||
}
|
||||
return Object.freeze(readonlyMap) as Map<string, AgentPermissionRequest>;
|
||||
}
|
||||
|
||||
private cloneValue<T>(value: T): T {
|
||||
if (value === null || typeof value !== "object") {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return new Date(value.getTime()) as T;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => this.cloneValue(item)) as T;
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
const clone = new Map();
|
||||
for (const [key, entryValue] of value.entries()) {
|
||||
clone.set(this.cloneValue(key), this.cloneValue(entryValue));
|
||||
}
|
||||
return clone as T;
|
||||
}
|
||||
if (value instanceof Set) {
|
||||
const clone = new Set();
|
||||
for (const entryValue of value.values()) {
|
||||
clone.add(this.cloneValue(entryValue));
|
||||
}
|
||||
return clone as T;
|
||||
}
|
||||
if (!this.isPlainObject(value)) {
|
||||
return value;
|
||||
}
|
||||
const clonedObject: Record<string, unknown> = {};
|
||||
for (const [key, entryValue] of Object.entries(value)) {
|
||||
clonedObject[key] = this.cloneValue(entryValue);
|
||||
}
|
||||
return clonedObject as T;
|
||||
}
|
||||
|
||||
private deepFreezeValue<T>(value: T, seen = new WeakSet<object>()): T {
|
||||
if (value === null || typeof value !== "object") {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
Object.freeze(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
const objectValue = value as object;
|
||||
if (seen.has(objectValue)) {
|
||||
return value;
|
||||
}
|
||||
seen.add(objectValue);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
this.deepFreezeValue(item, seen);
|
||||
}
|
||||
Object.freeze(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
for (const mapValue of value.values()) {
|
||||
this.deepFreezeValue(mapValue, seen);
|
||||
}
|
||||
Object.freeze(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value instanceof Set) {
|
||||
for (const setValue of value.values()) {
|
||||
this.deepFreezeValue(setValue, seen);
|
||||
}
|
||||
Object.freeze(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!this.isPlainObject(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const record = value as Record<string, unknown>;
|
||||
for (const key of Object.keys(record)) {
|
||||
this.deepFreezeValue(record[key], seen);
|
||||
}
|
||||
Object.freeze(record);
|
||||
return value;
|
||||
}
|
||||
|
||||
private isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
if (value === null || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return prototype === Object.prototype || prototype === null;
|
||||
}
|
||||
|
||||
private async normalizeConfig(
|
||||
@@ -845,7 +1035,7 @@ export class AgentManager {
|
||||
return client;
|
||||
}
|
||||
|
||||
private requireAgent(id: string): ManagedAgent {
|
||||
private requireAgent(id: string): ActiveManagedAgent {
|
||||
const agent = this.agents.get(id);
|
||||
if (!agent) {
|
||||
throw new Error(`Unknown agent '${id}'`);
|
||||
|
||||
Reference in New Issue
Block a user