@defai.digital/agent-domain 13.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +214 -0
- package/dist/enhanced-executor.d.ts +170 -0
- package/dist/enhanced-executor.d.ts.map +1 -0
- package/dist/enhanced-executor.js +1072 -0
- package/dist/enhanced-executor.js.map +1 -0
- package/dist/executor.d.ts +120 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +929 -0
- package/dist/executor.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +50 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +160 -0
- package/dist/loader.js.map +1 -0
- package/dist/persistent-registry.d.ts +105 -0
- package/dist/persistent-registry.d.ts.map +1 -0
- package/dist/persistent-registry.js +183 -0
- package/dist/persistent-registry.js.map +1 -0
- package/dist/production-factories.d.ts +70 -0
- package/dist/production-factories.d.ts.map +1 -0
- package/dist/production-factories.js +434 -0
- package/dist/production-factories.js.map +1 -0
- package/dist/prompt-executor.d.ts +119 -0
- package/dist/prompt-executor.d.ts.map +1 -0
- package/dist/prompt-executor.js +211 -0
- package/dist/prompt-executor.js.map +1 -0
- package/dist/registry.d.ts +57 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +123 -0
- package/dist/registry.js.map +1 -0
- package/dist/selection-service.d.ts +74 -0
- package/dist/selection-service.d.ts.map +1 -0
- package/dist/selection-service.js +322 -0
- package/dist/selection-service.js.map +1 -0
- package/dist/selector.d.ts +51 -0
- package/dist/selector.d.ts.map +1 -0
- package/dist/selector.js +249 -0
- package/dist/selector.js.map +1 -0
- package/dist/stub-checkpoint.d.ts +23 -0
- package/dist/stub-checkpoint.d.ts.map +1 -0
- package/dist/stub-checkpoint.js +137 -0
- package/dist/stub-checkpoint.js.map +1 -0
- package/dist/stub-delegation-tracker.d.ts +25 -0
- package/dist/stub-delegation-tracker.d.ts.map +1 -0
- package/dist/stub-delegation-tracker.js +118 -0
- package/dist/stub-delegation-tracker.js.map +1 -0
- package/dist/stub-parallel-executor.d.ts +19 -0
- package/dist/stub-parallel-executor.d.ts.map +1 -0
- package/dist/stub-parallel-executor.js +176 -0
- package/dist/stub-parallel-executor.js.map +1 -0
- package/dist/types.d.ts +614 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-templates.d.ts +117 -0
- package/dist/workflow-templates.d.ts.map +1 -0
- package/dist/workflow-templates.js +342 -0
- package/dist/workflow-templates.js.map +1 -0
- package/package.json +51 -0
- package/src/enhanced-executor.ts +1395 -0
- package/src/executor.ts +1153 -0
- package/src/index.ts +172 -0
- package/src/loader.ts +191 -0
- package/src/persistent-registry.ts +235 -0
- package/src/production-factories.ts +613 -0
- package/src/prompt-executor.ts +310 -0
- package/src/registry.ts +167 -0
- package/src/selection-service.ts +411 -0
- package/src/selector.ts +299 -0
- package/src/stub-checkpoint.ts +187 -0
- package/src/stub-delegation-tracker.ts +161 -0
- package/src/stub-parallel-executor.ts +224 -0
- package/src/types.ts +784 -0
- package/src/workflow-templates.ts +393 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @defai.digital/agent-domain
|
|
3
|
+
*
|
|
4
|
+
* Agent profiles and orchestration for AutomatosX.
|
|
5
|
+
* Manages specialized AI agents with defined workflows, personalities,
|
|
6
|
+
* abilities, and orchestration rules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Types
|
|
10
|
+
export type {
|
|
11
|
+
AgentRegistry,
|
|
12
|
+
AgentFilter,
|
|
13
|
+
AgentExecutor,
|
|
14
|
+
ExecutionStatus,
|
|
15
|
+
ExecutionProgress,
|
|
16
|
+
DelegationRequest,
|
|
17
|
+
DelegationManager,
|
|
18
|
+
AgentEventHandler,
|
|
19
|
+
AgentEventEmitter,
|
|
20
|
+
StepExecutorFn,
|
|
21
|
+
StepExecutionContext,
|
|
22
|
+
StepExecutionResult,
|
|
23
|
+
AgentDomainConfig,
|
|
24
|
+
// Prompt executor types
|
|
25
|
+
PromptExecutor,
|
|
26
|
+
PromptExecutionRequest,
|
|
27
|
+
PromptExecutionResponse,
|
|
28
|
+
PromptStepConfig,
|
|
29
|
+
// Tool executor types (INV-TOOL-001, INV-TOOL-002, INV-TOOL-003)
|
|
30
|
+
ToolExecutor,
|
|
31
|
+
// Loader types
|
|
32
|
+
AgentLoader,
|
|
33
|
+
AgentLoaderConfig,
|
|
34
|
+
// Selector types
|
|
35
|
+
AgentSelector,
|
|
36
|
+
AgentSelectionResult,
|
|
37
|
+
AgentSelectionContext,
|
|
38
|
+
// Ability integration types
|
|
39
|
+
AbilityManagerLike,
|
|
40
|
+
// Delegation tracker port types (DI pattern)
|
|
41
|
+
DelegationTrackerPort,
|
|
42
|
+
DelegationTrackerFactory,
|
|
43
|
+
// Checkpoint port types (DI pattern)
|
|
44
|
+
CheckpointStoragePort,
|
|
45
|
+
CheckpointManagerPort,
|
|
46
|
+
Checkpoint,
|
|
47
|
+
CheckpointStorageFactory,
|
|
48
|
+
CheckpointManagerFactory,
|
|
49
|
+
// Parallel executor port types (DI pattern)
|
|
50
|
+
ParallelExecutorPort,
|
|
51
|
+
ParallelStepExecutor,
|
|
52
|
+
ParallelStepResult,
|
|
53
|
+
ParallelGroupResult,
|
|
54
|
+
ParallelExecutorFactory,
|
|
55
|
+
} from './types.js';
|
|
56
|
+
|
|
57
|
+
export { DEFAULT_AGENT_DOMAIN_CONFIG } from './types.js';
|
|
58
|
+
|
|
59
|
+
// Registry
|
|
60
|
+
export {
|
|
61
|
+
InMemoryAgentRegistry,
|
|
62
|
+
AgentRegistryError,
|
|
63
|
+
createAgentRegistry,
|
|
64
|
+
} from './registry.js';
|
|
65
|
+
|
|
66
|
+
// Persistent Registry
|
|
67
|
+
export {
|
|
68
|
+
PersistentAgentRegistry,
|
|
69
|
+
createPersistentAgentRegistry,
|
|
70
|
+
getDefaultAgentStoragePath,
|
|
71
|
+
type PersistentRegistryConfig,
|
|
72
|
+
} from './persistent-registry.js';
|
|
73
|
+
|
|
74
|
+
// Executor
|
|
75
|
+
export { DefaultAgentExecutor, createAgentExecutor } from './executor.js';
|
|
76
|
+
|
|
77
|
+
// Enhanced Executor (full feature support)
|
|
78
|
+
export {
|
|
79
|
+
EnhancedAgentExecutor,
|
|
80
|
+
createEnhancedAgentExecutor,
|
|
81
|
+
type EnhancedAgentDomainConfig,
|
|
82
|
+
} from './enhanced-executor.js';
|
|
83
|
+
|
|
84
|
+
// Prompt Executor
|
|
85
|
+
export {
|
|
86
|
+
StubPromptExecutor,
|
|
87
|
+
ProviderPromptExecutor,
|
|
88
|
+
createStubPromptExecutor,
|
|
89
|
+
createProviderPromptExecutor,
|
|
90
|
+
type ProviderPromptExecutorConfig,
|
|
91
|
+
type ProviderRegistryLike,
|
|
92
|
+
type ProviderLike,
|
|
93
|
+
} from './prompt-executor.js';
|
|
94
|
+
|
|
95
|
+
// Loader
|
|
96
|
+
export { FileSystemAgentLoader, createAgentLoader } from './loader.js';
|
|
97
|
+
|
|
98
|
+
// Selector
|
|
99
|
+
export { KeywordAgentSelector, createAgentSelector } from './selector.js';
|
|
100
|
+
|
|
101
|
+
// Selection Service (INV-AGT-SEL-001 to INV-AGT-SEL-006)
|
|
102
|
+
export {
|
|
103
|
+
AgentSelectionService,
|
|
104
|
+
createAgentSelectionService,
|
|
105
|
+
type AgentSelectionServicePort,
|
|
106
|
+
} from './selection-service.js';
|
|
107
|
+
|
|
108
|
+
// Workflow Templates
|
|
109
|
+
export {
|
|
110
|
+
WORKFLOW_TEMPLATES,
|
|
111
|
+
PROMPT_RESPONSE_TEMPLATE,
|
|
112
|
+
RESEARCH_TEMPLATE,
|
|
113
|
+
CODE_REVIEW_TEMPLATE,
|
|
114
|
+
MULTI_STEP_TEMPLATE,
|
|
115
|
+
DELEGATE_CHAIN_TEMPLATE,
|
|
116
|
+
AGENT_SELECTION_TEMPLATE,
|
|
117
|
+
getWorkflowTemplate,
|
|
118
|
+
getAvailableTemplates,
|
|
119
|
+
createWorkflowFromTemplate,
|
|
120
|
+
isValidTemplateName,
|
|
121
|
+
getTemplateDescription,
|
|
122
|
+
type WorkflowTemplateName,
|
|
123
|
+
type WorkflowTemplate,
|
|
124
|
+
} from './workflow-templates.js';
|
|
125
|
+
|
|
126
|
+
// Stub factories (for DI fallbacks)
|
|
127
|
+
export { stubDelegationTrackerFactory, resetStubWarning as resetDelegationStubWarning } from './stub-delegation-tracker.js';
|
|
128
|
+
export { stubCheckpointStorageFactory, stubCheckpointManagerFactory, resetCheckpointStubWarning } from './stub-checkpoint.js';
|
|
129
|
+
export { stubParallelExecutorFactory, resetParallelStubWarning } from './stub-parallel-executor.js';
|
|
130
|
+
|
|
131
|
+
// Production factories (real implementations for wiring)
|
|
132
|
+
export {
|
|
133
|
+
createCheckpointStorageFactory,
|
|
134
|
+
createCheckpointManagerFactory,
|
|
135
|
+
createDelegationTrackerFactory,
|
|
136
|
+
createParallelExecutorFactory,
|
|
137
|
+
createProductionFactories,
|
|
138
|
+
type ProductionFactoriesConfig,
|
|
139
|
+
type ProductionFactories,
|
|
140
|
+
} from './production-factories.js';
|
|
141
|
+
|
|
142
|
+
// Re-export contract types for convenience
|
|
143
|
+
export type {
|
|
144
|
+
AgentProfile,
|
|
145
|
+
AgentRunOptions,
|
|
146
|
+
AgentResult,
|
|
147
|
+
AgentError,
|
|
148
|
+
AgentEvent,
|
|
149
|
+
AgentEventType,
|
|
150
|
+
AgentEventPayload,
|
|
151
|
+
AgentWorkflowStep,
|
|
152
|
+
AgentStepType,
|
|
153
|
+
AgentRetryPolicy,
|
|
154
|
+
AgentPersonality,
|
|
155
|
+
AbilitySelection,
|
|
156
|
+
OrchestrationConfig,
|
|
157
|
+
SelectionMetadata,
|
|
158
|
+
RedirectRule,
|
|
159
|
+
} from '@defai.digital/contracts';
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
AgentProfileSchema,
|
|
163
|
+
AgentRunOptionsSchema,
|
|
164
|
+
AgentResultSchema,
|
|
165
|
+
AgentErrorSchema,
|
|
166
|
+
AgentEventSchema,
|
|
167
|
+
AgentWorkflowStepSchema,
|
|
168
|
+
AgentErrorCode,
|
|
169
|
+
validateAgentProfile,
|
|
170
|
+
safeValidateAgentProfile,
|
|
171
|
+
validateAgentRunOptions,
|
|
172
|
+
} from '@defai.digital/contracts';
|
package/src/loader.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Profile Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads agent profiles from YAML/JSON files.
|
|
5
|
+
*
|
|
6
|
+
* Invariants:
|
|
7
|
+
* - INV-AGT-LDR-001: All loaded profiles must pass schema validation
|
|
8
|
+
* - INV-AGT-LDR-002: File extensions must match configured extensions
|
|
9
|
+
* - INV-AGT-LDR-003: Agent IDs must be unique across all loaded profiles
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { parse as parseYaml } from 'yaml';
|
|
15
|
+
import { validateAgentProfile, type AgentProfile } from '@defai.digital/contracts';
|
|
16
|
+
import type { AgentLoader, AgentLoaderConfig } from './types.js';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Constants
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
const DEFAULT_EXTENSIONS = ['.yaml', '.yml', '.json'];
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// File System Agent Loader
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Loads agent profiles from the file system
|
|
30
|
+
*/
|
|
31
|
+
export class FileSystemAgentLoader implements AgentLoader {
|
|
32
|
+
private readonly config: Required<AgentLoaderConfig>;
|
|
33
|
+
private cache = new Map<string, AgentProfile>();
|
|
34
|
+
private loaded = false;
|
|
35
|
+
|
|
36
|
+
constructor(config: AgentLoaderConfig) {
|
|
37
|
+
this.config = {
|
|
38
|
+
agentsDir: config.agentsDir,
|
|
39
|
+
extensions: config.extensions ?? DEFAULT_EXTENSIONS,
|
|
40
|
+
watch: config.watch ?? false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load an agent profile by ID
|
|
46
|
+
*/
|
|
47
|
+
async load(agentId: string): Promise<AgentProfile | undefined> {
|
|
48
|
+
if (!this.loaded) {
|
|
49
|
+
await this.loadAll();
|
|
50
|
+
}
|
|
51
|
+
return this.cache.get(agentId);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load all agent profiles from the directory
|
|
56
|
+
*/
|
|
57
|
+
async loadAll(): Promise<AgentProfile[]> {
|
|
58
|
+
this.cache.clear();
|
|
59
|
+
|
|
60
|
+
const dirPath = this.config.agentsDir;
|
|
61
|
+
|
|
62
|
+
// Check if directory exists
|
|
63
|
+
if (!fs.existsSync(dirPath)) {
|
|
64
|
+
this.loaded = true;
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const files = fs.readdirSync(dirPath);
|
|
69
|
+
const profiles: AgentProfile[] = [];
|
|
70
|
+
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
const ext = path.extname(file).toLowerCase();
|
|
73
|
+
if (!this.config.extensions.includes(ext)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const filePath = path.join(dirPath, file);
|
|
78
|
+
const profile = await this.loadFile(filePath);
|
|
79
|
+
|
|
80
|
+
if (profile) {
|
|
81
|
+
// INV-AGT-LDR-003: Check for duplicate agent IDs
|
|
82
|
+
if (this.cache.has(profile.agentId)) {
|
|
83
|
+
console.warn(
|
|
84
|
+
`Duplicate agent ID "${profile.agentId}" found in ${file}, skipping`
|
|
85
|
+
);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.cache.set(profile.agentId, profile);
|
|
90
|
+
profiles.push(profile);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.loaded = true;
|
|
95
|
+
return profiles;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if an agent exists
|
|
100
|
+
*/
|
|
101
|
+
async exists(agentId: string): Promise<boolean> {
|
|
102
|
+
if (!this.loaded) {
|
|
103
|
+
await this.loadAll();
|
|
104
|
+
}
|
|
105
|
+
return this.cache.has(agentId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Reload all agent profiles
|
|
110
|
+
*/
|
|
111
|
+
async reload(): Promise<void> {
|
|
112
|
+
this.loaded = false;
|
|
113
|
+
await this.loadAll();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Load a single agent profile from a file
|
|
118
|
+
*/
|
|
119
|
+
private async loadFile(filePath: string): Promise<AgentProfile | undefined> {
|
|
120
|
+
try {
|
|
121
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
122
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
123
|
+
|
|
124
|
+
let data: unknown;
|
|
125
|
+
if (ext === '.json') {
|
|
126
|
+
data = JSON.parse(content);
|
|
127
|
+
} else {
|
|
128
|
+
data = parseYaml(content);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Transform YAML fields to match schema
|
|
132
|
+
const transformed = this.transformYamlToProfile(data);
|
|
133
|
+
|
|
134
|
+
// INV-AGT-LDR-001: Validate against schema
|
|
135
|
+
const profile = validateAgentProfile(transformed);
|
|
136
|
+
return profile;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
139
|
+
console.warn(`Failed to load agent from ${filePath}: ${message}`);
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Transform YAML format to AgentProfile format
|
|
146
|
+
*/
|
|
147
|
+
private transformYamlToProfile(data: unknown): unknown {
|
|
148
|
+
if (typeof data !== 'object' || data === null) {
|
|
149
|
+
return data;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const yaml = data as Record<string, unknown>;
|
|
153
|
+
|
|
154
|
+
// Map YAML 'name' to 'agentId'
|
|
155
|
+
const profile: Record<string, unknown> = {
|
|
156
|
+
...yaml,
|
|
157
|
+
agentId: yaml.name ?? yaml.agentId,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Map 'abilities' array to 'abilities.core' if it's an array
|
|
161
|
+
if (Array.isArray(yaml.abilities)) {
|
|
162
|
+
profile.abilities = {
|
|
163
|
+
core: yaml.abilities,
|
|
164
|
+
taskBased: (yaml.abilitySelection as Record<string, unknown>)?.taskBased,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Map 'role' to description if description is missing
|
|
169
|
+
if (!yaml.description && yaml.role) {
|
|
170
|
+
profile.description = yaml.role as string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Remove YAML-specific fields
|
|
174
|
+
delete profile.name;
|
|
175
|
+
delete profile.abilitySelection;
|
|
176
|
+
delete profile.cognitiveFramework;
|
|
177
|
+
|
|
178
|
+
return profile;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// Factory Function
|
|
184
|
+
// ============================================================================
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Creates a file system agent loader
|
|
188
|
+
*/
|
|
189
|
+
export function createAgentLoader(config: AgentLoaderConfig): AgentLoader {
|
|
190
|
+
return new FileSystemAgentLoader(config);
|
|
191
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent Agent Registry
|
|
3
|
+
*
|
|
4
|
+
* Wraps the in-memory registry with file-based persistence.
|
|
5
|
+
* Agents are saved to a JSON file and loaded on startup.
|
|
6
|
+
*
|
|
7
|
+
* Invariants:
|
|
8
|
+
* - INV-AGT-PERSIST-001: All changes are persisted to disk
|
|
9
|
+
* - INV-AGT-PERSIST-002: Registry loads existing agents on initialization
|
|
10
|
+
* - INV-AGT-PERSIST-003: File operations are atomic (write to temp, then rename)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import {
|
|
16
|
+
validateAgentProfile,
|
|
17
|
+
type AgentProfile,
|
|
18
|
+
DATA_DIR_NAME,
|
|
19
|
+
AGENTS_FILENAME,
|
|
20
|
+
DEFAULT_SCHEMA_VERSION,
|
|
21
|
+
} from '@defai.digital/contracts';
|
|
22
|
+
import { InMemoryAgentRegistry } from './registry.js';
|
|
23
|
+
import type { AgentRegistry, AgentFilter } from './types.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configuration for persistent registry
|
|
27
|
+
*/
|
|
28
|
+
export interface PersistentRegistryConfig {
|
|
29
|
+
/**
|
|
30
|
+
* Path to the JSON file for storing agents
|
|
31
|
+
*/
|
|
32
|
+
storagePath: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether to create the directory if it doesn't exist
|
|
36
|
+
*/
|
|
37
|
+
createDir?: boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether to load existing agents on initialization
|
|
41
|
+
*/
|
|
42
|
+
loadOnInit?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Persistent agent registry that saves to JSON file
|
|
47
|
+
*/
|
|
48
|
+
export class PersistentAgentRegistry implements AgentRegistry {
|
|
49
|
+
private readonly inMemory: InMemoryAgentRegistry;
|
|
50
|
+
private readonly config: Required<PersistentRegistryConfig>;
|
|
51
|
+
private initialized = false;
|
|
52
|
+
|
|
53
|
+
constructor(config: PersistentRegistryConfig) {
|
|
54
|
+
this.inMemory = new InMemoryAgentRegistry();
|
|
55
|
+
this.config = {
|
|
56
|
+
storagePath: config.storagePath,
|
|
57
|
+
createDir: config.createDir ?? true,
|
|
58
|
+
loadOnInit: config.loadOnInit ?? true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Initialize the registry (load from disk if configured)
|
|
64
|
+
*/
|
|
65
|
+
private async ensureInitialized(): Promise<void> {
|
|
66
|
+
if (this.initialized) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this.config.loadOnInit) {
|
|
71
|
+
await this.loadFromDisk();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.initialized = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Register a new agent profile
|
|
79
|
+
* INV-AGT-PERSIST-001: Persists to disk after registration
|
|
80
|
+
*/
|
|
81
|
+
async register(profile: AgentProfile): Promise<void> {
|
|
82
|
+
await this.ensureInitialized();
|
|
83
|
+
await this.inMemory.register(profile);
|
|
84
|
+
await this.saveToDisk();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get an agent by ID
|
|
89
|
+
*/
|
|
90
|
+
async get(agentId: string): Promise<AgentProfile | undefined> {
|
|
91
|
+
await this.ensureInitialized();
|
|
92
|
+
return this.inMemory.get(agentId);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* List all registered agents with optional filtering
|
|
97
|
+
*/
|
|
98
|
+
async list(filter?: AgentFilter): Promise<AgentProfile[]> {
|
|
99
|
+
await this.ensureInitialized();
|
|
100
|
+
return this.inMemory.list(filter);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Update an agent profile
|
|
105
|
+
* INV-AGT-PERSIST-001: Persists to disk after update
|
|
106
|
+
*/
|
|
107
|
+
async update(agentId: string, updates: Partial<AgentProfile>): Promise<void> {
|
|
108
|
+
await this.ensureInitialized();
|
|
109
|
+
await this.inMemory.update(agentId, updates);
|
|
110
|
+
await this.saveToDisk();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Remove an agent
|
|
115
|
+
* INV-AGT-PERSIST-001: Persists to disk after removal
|
|
116
|
+
*/
|
|
117
|
+
async remove(agentId: string): Promise<void> {
|
|
118
|
+
await this.ensureInitialized();
|
|
119
|
+
await this.inMemory.remove(agentId);
|
|
120
|
+
await this.saveToDisk();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if an agent exists
|
|
125
|
+
*/
|
|
126
|
+
async exists(agentId: string): Promise<boolean> {
|
|
127
|
+
await this.ensureInitialized();
|
|
128
|
+
return this.inMemory.exists(agentId);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Load agents from disk
|
|
133
|
+
* INV-AGT-PERSIST-002: Called on initialization
|
|
134
|
+
*/
|
|
135
|
+
private async loadFromDisk(): Promise<void> {
|
|
136
|
+
const filePath = this.config.storagePath;
|
|
137
|
+
|
|
138
|
+
if (!fs.existsSync(filePath)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
144
|
+
const data = JSON.parse(content);
|
|
145
|
+
|
|
146
|
+
if (!Array.isArray(data.agents)) {
|
|
147
|
+
console.warn(`Invalid agents file format at ${filePath}`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const agentData of data.agents) {
|
|
152
|
+
try {
|
|
153
|
+
const profile = validateAgentProfile(agentData);
|
|
154
|
+
// Use internal method to avoid triggering save
|
|
155
|
+
await this.inMemory.register(profile);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
158
|
+
console.warn(`Failed to load agent "${agentData.agentId}": ${message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
163
|
+
console.warn(`Failed to load agents from ${filePath}: ${message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Save agents to disk
|
|
169
|
+
* INV-AGT-PERSIST-003: Atomic write using temp file + rename
|
|
170
|
+
*/
|
|
171
|
+
private async saveToDisk(): Promise<void> {
|
|
172
|
+
const filePath = this.config.storagePath;
|
|
173
|
+
const dirPath = path.dirname(filePath);
|
|
174
|
+
|
|
175
|
+
// Create directory if needed
|
|
176
|
+
if (this.config.createDir && !fs.existsSync(dirPath)) {
|
|
177
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Get all agents
|
|
181
|
+
const agents = await this.inMemory.list();
|
|
182
|
+
|
|
183
|
+
// Create data structure
|
|
184
|
+
const data = {
|
|
185
|
+
version: DEFAULT_SCHEMA_VERSION,
|
|
186
|
+
updatedAt: new Date().toISOString(),
|
|
187
|
+
agents,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// INV-AGT-PERSIST-003: Write to temp file first, then rename
|
|
191
|
+
const tempPath = `${filePath}.tmp`;
|
|
192
|
+
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
193
|
+
fs.renameSync(tempPath, filePath);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get the number of registered agents
|
|
198
|
+
*/
|
|
199
|
+
get size(): number {
|
|
200
|
+
return this.inMemory.size;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Force reload from disk
|
|
205
|
+
*/
|
|
206
|
+
async reload(): Promise<void> {
|
|
207
|
+
this.inMemory.clear();
|
|
208
|
+
this.initialized = false;
|
|
209
|
+
await this.ensureInitialized();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Creates a persistent agent registry
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* const registry = createPersistentAgentRegistry({
|
|
219
|
+
* storagePath: getDefaultAgentStoragePath(),
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
export function createPersistentAgentRegistry(
|
|
224
|
+
config: PersistentRegistryConfig
|
|
225
|
+
): AgentRegistry {
|
|
226
|
+
return new PersistentAgentRegistry(config);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Gets the default storage path for agents
|
|
231
|
+
* Uses DATA_DIR_NAME/AGENTS_FILENAME in current working directory
|
|
232
|
+
*/
|
|
233
|
+
export function getDefaultAgentStoragePath(): string {
|
|
234
|
+
return path.join(process.cwd(), DATA_DIR_NAME, AGENTS_FILENAME);
|
|
235
|
+
}
|