@auxiora/runtime 1.3.0 → 1.10.0
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/dist/enrichment/__tests__/architect-stage.test.d.ts +2 -0
- package/dist/enrichment/__tests__/architect-stage.test.d.ts.map +1 -0
- package/dist/enrichment/__tests__/architect-stage.test.js +189 -0
- package/dist/enrichment/__tests__/architect-stage.test.js.map +1 -0
- package/dist/enrichment/__tests__/integration.test.d.ts +2 -0
- package/dist/enrichment/__tests__/integration.test.d.ts.map +1 -0
- package/dist/enrichment/__tests__/integration.test.js +79 -0
- package/dist/enrichment/__tests__/integration.test.js.map +1 -0
- package/dist/enrichment/__tests__/memory-stage.test.d.ts +2 -0
- package/dist/enrichment/__tests__/memory-stage.test.d.ts.map +1 -0
- package/dist/enrichment/__tests__/memory-stage.test.js +43 -0
- package/dist/enrichment/__tests__/memory-stage.test.js.map +1 -0
- package/dist/enrichment/__tests__/mode-stage.test.d.ts +2 -0
- package/dist/enrichment/__tests__/mode-stage.test.d.ts.map +1 -0
- package/dist/enrichment/__tests__/mode-stage.test.js +139 -0
- package/dist/enrichment/__tests__/mode-stage.test.js.map +1 -0
- package/dist/enrichment/__tests__/model-identity-stage.test.d.ts +2 -0
- package/dist/enrichment/__tests__/model-identity-stage.test.d.ts.map +1 -0
- package/dist/enrichment/__tests__/model-identity-stage.test.js +74 -0
- package/dist/enrichment/__tests__/model-identity-stage.test.js.map +1 -0
- package/dist/enrichment/__tests__/pipeline.test.d.ts +2 -0
- package/dist/enrichment/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/enrichment/__tests__/pipeline.test.js +78 -0
- package/dist/enrichment/__tests__/pipeline.test.js.map +1 -0
- package/dist/enrichment/__tests__/self-awareness-stage.test.d.ts +2 -0
- package/dist/enrichment/__tests__/self-awareness-stage.test.d.ts.map +1 -0
- package/dist/enrichment/__tests__/self-awareness-stage.test.js +71 -0
- package/dist/enrichment/__tests__/self-awareness-stage.test.js.map +1 -0
- package/dist/enrichment/__tests__/types.test.d.ts +2 -0
- package/dist/enrichment/__tests__/types.test.d.ts.map +1 -0
- package/dist/enrichment/__tests__/types.test.js +31 -0
- package/dist/enrichment/__tests__/types.test.js.map +1 -0
- package/dist/enrichment/index.d.ts +8 -0
- package/dist/enrichment/index.d.ts.map +1 -0
- package/dist/enrichment/index.js +7 -0
- package/dist/enrichment/index.js.map +1 -0
- package/dist/enrichment/pipeline.d.ts +7 -0
- package/dist/enrichment/pipeline.d.ts.map +1 -0
- package/dist/enrichment/pipeline.js +29 -0
- package/dist/enrichment/pipeline.js.map +1 -0
- package/dist/enrichment/stages/architect-stage.d.ts +57 -0
- package/dist/enrichment/stages/architect-stage.d.ts.map +1 -0
- package/dist/enrichment/stages/architect-stage.js +112 -0
- package/dist/enrichment/stages/architect-stage.js.map +1 -0
- package/dist/enrichment/stages/memory-stage.d.ts +15 -0
- package/dist/enrichment/stages/memory-stage.d.ts.map +1 -0
- package/dist/enrichment/stages/memory-stage.js +19 -0
- package/dist/enrichment/stages/memory-stage.js.map +1 -0
- package/dist/enrichment/stages/mode-stage.d.ts +49 -0
- package/dist/enrichment/stages/mode-stage.d.ts.map +1 -0
- package/dist/enrichment/stages/mode-stage.js +60 -0
- package/dist/enrichment/stages/mode-stage.js.map +1 -0
- package/dist/enrichment/stages/model-identity-stage.d.ts +24 -0
- package/dist/enrichment/stages/model-identity-stage.d.ts.map +1 -0
- package/dist/enrichment/stages/model-identity-stage.js +23 -0
- package/dist/enrichment/stages/model-identity-stage.js.map +1 -0
- package/dist/enrichment/stages/self-awareness-stage.d.ts +23 -0
- package/dist/enrichment/stages/self-awareness-stage.d.ts.map +1 -0
- package/dist/enrichment/stages/self-awareness-stage.js +24 -0
- package/dist/enrichment/stages/self-awareness-stage.js.map +1 -0
- package/dist/enrichment/types.d.ts +45 -0
- package/dist/enrichment/types.d.ts.map +1 -0
- package/dist/enrichment/types.js +2 -0
- package/dist/enrichment/types.js.map +1 -0
- package/dist/index.d.ts +77 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3233 -230
- package/dist/index.js.map +1 -1
- package/dist/transparency/__tests__/collector.test.d.ts +2 -0
- package/dist/transparency/__tests__/collector.test.d.ts.map +1 -0
- package/dist/transparency/__tests__/collector.test.js +133 -0
- package/dist/transparency/__tests__/collector.test.js.map +1 -0
- package/dist/transparency/__tests__/confidence-scorer.test.d.ts +2 -0
- package/dist/transparency/__tests__/confidence-scorer.test.d.ts.map +1 -0
- package/dist/transparency/__tests__/confidence-scorer.test.js +118 -0
- package/dist/transparency/__tests__/confidence-scorer.test.js.map +1 -0
- package/dist/transparency/__tests__/source-attributor.test.d.ts +2 -0
- package/dist/transparency/__tests__/source-attributor.test.d.ts.map +1 -0
- package/dist/transparency/__tests__/source-attributor.test.js +47 -0
- package/dist/transparency/__tests__/source-attributor.test.js.map +1 -0
- package/dist/transparency/__tests__/types.test.d.ts +2 -0
- package/dist/transparency/__tests__/types.test.d.ts.map +1 -0
- package/dist/transparency/__tests__/types.test.js +62 -0
- package/dist/transparency/__tests__/types.test.js.map +1 -0
- package/dist/transparency/collector.d.ts +30 -0
- package/dist/transparency/collector.d.ts.map +1 -0
- package/dist/transparency/collector.js +79 -0
- package/dist/transparency/collector.js.map +1 -0
- package/dist/transparency/confidence-scorer.d.ts +17 -0
- package/dist/transparency/confidence-scorer.d.ts.map +1 -0
- package/dist/transparency/confidence-scorer.js +66 -0
- package/dist/transparency/confidence-scorer.js.map +1 -0
- package/dist/transparency/index.d.ts +8 -0
- package/dist/transparency/index.d.ts.map +1 -0
- package/dist/transparency/index.js +4 -0
- package/dist/transparency/index.js.map +1 -0
- package/dist/transparency/source-attributor.d.ts +10 -0
- package/dist/transparency/source-attributor.d.ts.map +1 -0
- package/dist/transparency/source-attributor.js +42 -0
- package/dist/transparency/source-attributor.js.map +1 -0
- package/dist/transparency/types.d.ts +53 -0
- package/dist/transparency/types.d.ts.map +1 -0
- package/dist/transparency/types.js +2 -0
- package/dist/transparency/types.js.map +1 -0
- package/package.json +74 -50
package/dist/index.js
CHANGED
|
@@ -10,12 +10,12 @@ import { audit } from '@auxiora/audit';
|
|
|
10
10
|
import { getWorkspacePath, getSoulPath, getAgentsPath, getIdentityPath, getUserPath, getBehaviorsPath, getWebhooksPath, getScreenshotsDir, } from '@auxiora/core';
|
|
11
11
|
import { createArchitect, ARCHITECT_BASE_PROMPT, VaultStorageAdapter } from '@auxiora/personality/architect';
|
|
12
12
|
import { toolRegistry, toolExecutor, initializeToolExecutor, ToolPermission, setBrowserManager, setWebhookManager, setBehaviorManager, setProviderFactory, setOrchestrationEngine, setResearchEngine, setClipboardMonitor, setAppController, setSystemStateMonitor, setEmailIntelligence, setCalendarIntelligence, setContactGraph, setContextRecall, setComposeEngine, setGrammarChecker, setLanguageDetector, } from '@auxiora/tools';
|
|
13
|
-
import { ResearchEngine } from '@auxiora/research';
|
|
13
|
+
import { ResearchEngine, ResearchIntentDetector, DeepResearchOrchestrator, ReportGenerator } from '@auxiora/research';
|
|
14
14
|
import { OrchestrationEngine } from '@auxiora/orchestrator';
|
|
15
15
|
import * as crypto from 'node:crypto';
|
|
16
16
|
import * as fs from 'node:fs/promises';
|
|
17
17
|
import * as path from 'node:path';
|
|
18
|
-
import { BehaviorManager } from '@auxiora/behaviors';
|
|
18
|
+
import { BehaviorManager, evaluateConditions } from '@auxiora/behaviors';
|
|
19
19
|
import { BrowserManager } from '@auxiora/browser';
|
|
20
20
|
import { ClipboardMonitor, AppController, SystemStateMonitor } from '@auxiora/os-bridge';
|
|
21
21
|
import { VoiceManager } from '@auxiora/voice';
|
|
@@ -31,7 +31,8 @@ import { createLoopDetectionState, recordToolCall, recordToolOutcome, detectLoop
|
|
|
31
31
|
import { UserManager } from '@auxiora/social';
|
|
32
32
|
import { WorkflowEngine, ApprovalManager, AutonomousExecutor } from '@auxiora/workflows';
|
|
33
33
|
import { AgentProtocol, MessageSigner, AgentDirectory } from '@auxiora/agent-protocol';
|
|
34
|
-
import {
|
|
34
|
+
import { Updater, InstallationDetector, VersionChecker, HealthChecker, createStrategyMap } from '@auxiora/updater';
|
|
35
|
+
import { AmbientPatternEngine, QuietNotificationManager, BriefingGenerator, AnticipationEngine, AmbientScheduler, DEFAULT_AMBIENT_SCHEDULER_CONFIG, NotificationOrchestrator, AmbientAwarenessCollector } from '@auxiora/ambient';
|
|
35
36
|
import { NotificationHub, DoNotDisturbManager } from '@auxiora/notification-hub';
|
|
36
37
|
import { ConnectorRegistry, AuthManager as ConnectorAuthManager, TriggerManager } from '@auxiora/connectors';
|
|
37
38
|
import { googleWorkspaceConnector } from '@auxiora/connector-google-workspace';
|
|
@@ -48,6 +49,24 @@ import { ContactGraph, ContextRecall } from '@auxiora/contacts';
|
|
|
48
49
|
import { ComposeEngine, GrammarChecker, LanguageDetector } from '@auxiora/compose';
|
|
49
50
|
import { ScreenCapturer } from '@auxiora/screen';
|
|
50
51
|
import { CapabilityCatalogImpl, HealthMonitorImpl, createIntrospectTool, generatePromptFragment } from '@auxiora/introspection';
|
|
52
|
+
import { JobQueue } from '@auxiora/job-queue';
|
|
53
|
+
import { Consciousness } from '@auxiora/consciousness';
|
|
54
|
+
import { McpClientManager } from '@auxiora/mcp';
|
|
55
|
+
import { GuardrailPipeline } from '@auxiora/guardrails';
|
|
56
|
+
import { collectTransparencyMeta } from './transparency/index.js';
|
|
57
|
+
import { IntentParser as NLIntentParser, AutomationBuilder } from '@auxiora/nl-automation';
|
|
58
|
+
import { BranchManager } from '@auxiora/conversation-branch';
|
|
59
|
+
import { ReActLoop } from '@auxiora/react-loop';
|
|
60
|
+
import { AgentCardBuilder, TaskManager as A2ATaskManager } from '@auxiora/a2a';
|
|
61
|
+
import { CanvasSession } from '@auxiora/canvas';
|
|
62
|
+
import { DocumentStore, ContextBuilder } from '@auxiora/rag';
|
|
63
|
+
import { GraphStore, EntityLinker } from '@auxiora/knowledge-graph';
|
|
64
|
+
import { EvalRunner, EvalStore, exactMatch, containsExpected, lengthRatio, keywordCoverage, sentenceCompleteness, responseRelevance, toxicityScore } from '@auxiora/evaluation';
|
|
65
|
+
import { CodeExecutor, SessionManager as CodeSessionManager } from '@auxiora/code-interpreter';
|
|
66
|
+
import { ImageGenManager, OpenAIImageProvider, ReplicateImageProvider } from '@auxiora/image-gen';
|
|
67
|
+
import { BackupManager } from '@auxiora/backup';
|
|
68
|
+
import { ApprovalQueue } from '@auxiora/approval-queue';
|
|
69
|
+
import { VectorStore } from '@auxiora/vector-store';
|
|
51
70
|
import { setMemoryStore } from '@auxiora/tools';
|
|
52
71
|
import { getAuditLogger } from '@auxiora/audit';
|
|
53
72
|
import { Router } from 'express';
|
|
@@ -57,10 +76,20 @@ import { getModesDir } from '@auxiora/core';
|
|
|
57
76
|
import { fileURLToPath } from 'node:url';
|
|
58
77
|
import { getLogger, generateRequestId, runWithRequestId } from '@auxiora/logger';
|
|
59
78
|
import { SelfAwarenessAssembler, InMemoryAwarenessStorage, ConversationReflector, CapacityMonitor, KnowledgeBoundary, RelationshipModel, TemporalTracker, EnvironmentSensor, MetaCognitor, } from '@auxiora/self-awareness';
|
|
79
|
+
import { EnrichmentPipeline, MemoryStage, ModeStage, ArchitectStage, SelfAwarenessStage } from './enrichment/index.js';
|
|
60
80
|
/**
|
|
61
81
|
* Map Claude Code emulation tool calls to our actual tool names + input format.
|
|
62
82
|
* The model may call CC tools (WebSearch, Bash, etc.) since they're in the request for OAuth compat.
|
|
63
83
|
*/
|
|
84
|
+
/**
|
|
85
|
+
* Claude Code internal tools that have no Auxiora equivalent.
|
|
86
|
+
* When the model tries to call these, we return a helpful error message
|
|
87
|
+
* instead of letting them hit the tool executor and waste a round-trip.
|
|
88
|
+
*/
|
|
89
|
+
const CC_ONLY_TOOLS = new Set([
|
|
90
|
+
'EnterPlanMode', 'ExitPlanMode', 'AskUserQuestion',
|
|
91
|
+
'NotebookEdit', 'Skill', 'Task', 'TaskOutput',
|
|
92
|
+
]);
|
|
64
93
|
function mapCCToolCall(name, input) {
|
|
65
94
|
switch (name) {
|
|
66
95
|
case 'WebSearch':
|
|
@@ -73,7 +102,16 @@ function mapCCToolCall(name, input) {
|
|
|
73
102
|
return { name: 'file_read', input: { path: input.file_path } };
|
|
74
103
|
case 'Write':
|
|
75
104
|
return { name: 'file_write', input: { path: input.file_path, content: input.content } };
|
|
105
|
+
case 'Edit':
|
|
106
|
+
return { name: 'file_write', input: { path: input.file_path, content: input.new_string } };
|
|
107
|
+
case 'Glob':
|
|
108
|
+
return { name: 'file_list', input: { path: input.path || '.', pattern: input.pattern } };
|
|
109
|
+
case 'Grep':
|
|
110
|
+
return { name: 'bash', input: { command: `grep -r "${(input.pattern || '').replace(/"/g, '\\"')}" ${input.path || '.'} --include="${input.glob || '*'}" -l 2>/dev/null | head -20` } };
|
|
76
111
|
default:
|
|
112
|
+
if (CC_ONLY_TOOLS.has(name)) {
|
|
113
|
+
return { name, input, skip: `Tool "${name}" is not available. Do not use Claude Code internal tools. Respond using text only or use the available tools: bash, web_browser, file_read, file_write, file_list.` };
|
|
114
|
+
}
|
|
77
115
|
return { name, input };
|
|
78
116
|
}
|
|
79
117
|
}
|
|
@@ -113,6 +151,7 @@ export class Auxiora {
|
|
|
113
151
|
intentParser;
|
|
114
152
|
actionPlanner;
|
|
115
153
|
orchestrationEngine;
|
|
154
|
+
jobQueue;
|
|
116
155
|
// [P14] Team / Social
|
|
117
156
|
userManager;
|
|
118
157
|
workflowEngine;
|
|
@@ -139,18 +178,58 @@ export class Auxiora {
|
|
|
139
178
|
connectorAuthManager;
|
|
140
179
|
triggerManager;
|
|
141
180
|
ambientScheduler;
|
|
181
|
+
ambientDetectTimer;
|
|
182
|
+
ambientAwarenessCollector;
|
|
142
183
|
notificationHub;
|
|
143
184
|
dndManager;
|
|
144
185
|
notificationOrchestrator;
|
|
186
|
+
researchEngine;
|
|
187
|
+
intentDetector = new ResearchIntentDetector();
|
|
188
|
+
researchJobs = new Map();
|
|
189
|
+
researchJobExpiry;
|
|
145
190
|
capabilityCatalog;
|
|
146
191
|
healthMonitor;
|
|
147
192
|
capabilityPromptFragment = '';
|
|
148
193
|
selfAwarenessAssembler;
|
|
149
194
|
architect;
|
|
150
195
|
architectBridge = null;
|
|
196
|
+
architectResetChats = new Set();
|
|
151
197
|
architectAwarenessCollector = null;
|
|
198
|
+
enrichmentPipeline;
|
|
199
|
+
lastToolsUsed = new Map();
|
|
200
|
+
consciousness;
|
|
201
|
+
mcpClientManager;
|
|
202
|
+
selfModelCache;
|
|
203
|
+
userModelCache;
|
|
204
|
+
static MODEL_CACHE_TTL = 60_000;
|
|
152
205
|
// Security floor
|
|
153
206
|
securityFloor;
|
|
207
|
+
guardrailPipeline;
|
|
208
|
+
evalRunner;
|
|
209
|
+
evalStore;
|
|
210
|
+
documentStore;
|
|
211
|
+
contextBuilder;
|
|
212
|
+
knowledgeGraph;
|
|
213
|
+
entityLinker;
|
|
214
|
+
imageGenManager;
|
|
215
|
+
updater;
|
|
216
|
+
installationDetector;
|
|
217
|
+
versionChecker;
|
|
218
|
+
codeExecutor;
|
|
219
|
+
codeSessionManager;
|
|
220
|
+
backupManager;
|
|
221
|
+
backupStore = new Map();
|
|
222
|
+
nlIntentParser = new NLIntentParser();
|
|
223
|
+
automationBuilder = new AutomationBuilder();
|
|
224
|
+
branchManagers = new Map();
|
|
225
|
+
approvalQueue;
|
|
226
|
+
vectorStore;
|
|
227
|
+
reactLoops = new Map();
|
|
228
|
+
reactResults = new Map();
|
|
229
|
+
a2aTaskManager = new A2ATaskManager();
|
|
230
|
+
a2aAgentCard;
|
|
231
|
+
canvasSessions = new Map();
|
|
232
|
+
sandboxManager;
|
|
154
233
|
sessionEscalation = new Map();
|
|
155
234
|
/** Tracks the most recent channel ID for each connected channel type (e.g. discord → snowflake).
|
|
156
235
|
* Used for proactive delivery (behaviors, ambient briefings). Persisted to disk. */
|
|
@@ -178,6 +257,23 @@ export class Auxiora {
|
|
|
178
257
|
this.logger.debug('Auto-approving tool', { toolName, params });
|
|
179
258
|
return true;
|
|
180
259
|
});
|
|
260
|
+
// Initialize MCP client connections
|
|
261
|
+
if (this.config.mcp && Object.keys(this.config.mcp.servers).length > 0) {
|
|
262
|
+
try {
|
|
263
|
+
this.mcpClientManager = new McpClientManager(toolRegistry, this.config.mcp);
|
|
264
|
+
await this.mcpClientManager.connectAll();
|
|
265
|
+
const status = this.mcpClientManager.getStatus();
|
|
266
|
+
this.logger.info('MCP client initialized', {
|
|
267
|
+
servers: status.size,
|
|
268
|
+
tools: [...status.values()].reduce((sum, s) => sum + s.toolCount, 0),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
this.logger.warn('Failed to initialize MCP client', {
|
|
273
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
181
277
|
// Initialize sessions
|
|
182
278
|
this.sessions = new SessionManager({
|
|
183
279
|
maxContextTokens: this.config.session.maxContextTokens,
|
|
@@ -226,6 +322,7 @@ export class Auxiora {
|
|
|
226
322
|
searchTimeout: this.config.research?.searchTimeout ?? 10_000,
|
|
227
323
|
fetchTimeout: this.config.research?.fetchTimeout ?? 15_000,
|
|
228
324
|
});
|
|
325
|
+
this.researchEngine = researchEngine;
|
|
229
326
|
setResearchEngine(researchEngine);
|
|
230
327
|
this.logger.info(`Research engine initialized (Brave Search configured${provider ? ', AI extraction enabled' : ''})`);
|
|
231
328
|
}
|
|
@@ -240,6 +337,104 @@ export class Auxiora {
|
|
|
240
337
|
await this.loadPersonality();
|
|
241
338
|
// Initialize modes system
|
|
242
339
|
await this.initializeModes();
|
|
340
|
+
// Initialize guardrails pipeline (if enabled)
|
|
341
|
+
if (this.config.guardrails?.enabled !== false) {
|
|
342
|
+
this.guardrailPipeline = new GuardrailPipeline({
|
|
343
|
+
piiDetection: this.config.guardrails?.piiDetection,
|
|
344
|
+
promptInjection: this.config.guardrails?.promptInjection,
|
|
345
|
+
toxicityFilter: this.config.guardrails?.toxicityFilter,
|
|
346
|
+
blockThreshold: this.config.guardrails?.blockThreshold,
|
|
347
|
+
redactPii: this.config.guardrails?.redactPii,
|
|
348
|
+
});
|
|
349
|
+
this.logger.info('Guardrails pipeline initialized');
|
|
350
|
+
}
|
|
351
|
+
// Initialize evaluation system
|
|
352
|
+
this.evalStore = new EvalStore();
|
|
353
|
+
this.evalRunner = new EvalRunner({
|
|
354
|
+
exactMatch,
|
|
355
|
+
containsExpected,
|
|
356
|
+
lengthRatio,
|
|
357
|
+
keywordCoverage,
|
|
358
|
+
sentenceCompleteness,
|
|
359
|
+
responseRelevance,
|
|
360
|
+
toxicityScore,
|
|
361
|
+
});
|
|
362
|
+
this.logger.info('Evaluation system initialized');
|
|
363
|
+
// Initialize backup manager
|
|
364
|
+
this.backupManager = new BackupManager();
|
|
365
|
+
this.logger.info('Backup manager initialized');
|
|
366
|
+
// Initialize approval queue
|
|
367
|
+
this.approvalQueue = new ApprovalQueue();
|
|
368
|
+
this.logger.info('Approval queue initialized');
|
|
369
|
+
// Initialize vector store
|
|
370
|
+
this.vectorStore = new VectorStore({ dimensions: 1536, maxEntries: 100_000 });
|
|
371
|
+
this.logger.info('Vector store initialized');
|
|
372
|
+
// Initialize consciousness orchestrator (self-model, journal, monitor, repair)
|
|
373
|
+
if (this.architect && this.healthMonitor) {
|
|
374
|
+
try {
|
|
375
|
+
this.consciousness = new Consciousness({
|
|
376
|
+
vault: this.vault,
|
|
377
|
+
healthMonitor: this.healthMonitor,
|
|
378
|
+
feedbackStore: {
|
|
379
|
+
getInsights: () => {
|
|
380
|
+
const raw = this.architect.getFeedbackInsights();
|
|
381
|
+
return {
|
|
382
|
+
...raw,
|
|
383
|
+
suggestedAdjustments: raw.suggestedAdjustments,
|
|
384
|
+
};
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
correctionStore: {
|
|
388
|
+
getStats: () => this.architect.getCorrectionStats(),
|
|
389
|
+
},
|
|
390
|
+
preferenceHistory: {
|
|
391
|
+
detectConflicts: () => this.architect.getPreferenceConflicts(),
|
|
392
|
+
},
|
|
393
|
+
getResourceMetrics: () => {
|
|
394
|
+
const mem = process.memoryUsage();
|
|
395
|
+
return {
|
|
396
|
+
memoryUsageMb: Math.round(mem.heapUsed / 1024 / 1024),
|
|
397
|
+
cpuPercent: 0, // CPU % requires sampling; omit for now
|
|
398
|
+
activeConnections: this.gateway?.getConnections().length ?? 0,
|
|
399
|
+
uptimeSeconds: Math.round(process.uptime()),
|
|
400
|
+
};
|
|
401
|
+
},
|
|
402
|
+
getCapabilityMetrics: () => {
|
|
403
|
+
const tools = toolRegistry.list();
|
|
404
|
+
return {
|
|
405
|
+
totalCapabilities: tools.length,
|
|
406
|
+
healthyCapabilities: tools.length,
|
|
407
|
+
degradedCapabilities: [],
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
actionExecutor: async (command) => {
|
|
411
|
+
this.logger.info('Consciousness repair action (log-only)', { command });
|
|
412
|
+
return `[log-only] ${command}`;
|
|
413
|
+
},
|
|
414
|
+
onNotify: (diagnosis, action) => {
|
|
415
|
+
this.logger.info('Consciousness repair notification', {
|
|
416
|
+
diagnosis: diagnosis?.description,
|
|
417
|
+
action: action.description,
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
onApprovalRequest: async () => false, // deny auto-repair initially
|
|
421
|
+
decisionLog: {
|
|
422
|
+
query: (q) => this.architect.queryDecisions(q),
|
|
423
|
+
getDueFollowUps: () => this.architect.getDueFollowUps(),
|
|
424
|
+
},
|
|
425
|
+
version: '1.4.0',
|
|
426
|
+
monitorIntervalMs: 60_000,
|
|
427
|
+
});
|
|
428
|
+
await this.consciousness.initialize();
|
|
429
|
+
this.logger.info('Consciousness orchestrator initialized');
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
this.logger.warn('Failed to initialize consciousness', { error: err instanceof Error ? err : new Error(String(err)) });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Build enrichment pipeline from available subsystems
|
|
436
|
+
this.buildEnrichmentPipeline();
|
|
437
|
+
this.logger.info('Enrichment pipeline initialized');
|
|
243
438
|
// Initialize gateway
|
|
244
439
|
this.gateway = new Gateway({
|
|
245
440
|
config: this.config,
|
|
@@ -265,20 +460,48 @@ export class Auxiora {
|
|
|
265
460
|
this.gateway.broadcast({ type: 'activity', payload: entry }, (client) => client.authenticated);
|
|
266
461
|
}
|
|
267
462
|
};
|
|
463
|
+
// Initialize durable job queue
|
|
464
|
+
const jobQueueDbPath = path.join(path.dirname(getBehaviorsPath()), 'jobs.db');
|
|
465
|
+
this.jobQueue = new JobQueue(jobQueueDbPath, {
|
|
466
|
+
pollIntervalMs: 2000,
|
|
467
|
+
concurrency: 5,
|
|
468
|
+
});
|
|
469
|
+
// Register behavior handler
|
|
470
|
+
this.jobQueue.register('behavior', async (payload) => {
|
|
471
|
+
if (this.behaviors) {
|
|
472
|
+
await this.behaviors.executeNow(payload.behaviorId);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
// Register ambient pattern flush handler (re-enqueues itself)
|
|
476
|
+
this.jobQueue.register('ambient-flush', async (_payload, ctx) => {
|
|
477
|
+
if (this.ambientEngine) {
|
|
478
|
+
const serialized = this.ambientEngine.serialize();
|
|
479
|
+
ctx.checkpoint(serialized);
|
|
480
|
+
}
|
|
481
|
+
// Re-enqueue next flush in 5 minutes
|
|
482
|
+
if (this.jobQueue) {
|
|
483
|
+
this.jobQueue.enqueue('ambient-flush', {}, { scheduledAt: Date.now() + 5 * 60 * 1000 });
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
this.jobQueue.start();
|
|
487
|
+
this.logger.info('Durable job queue initialized');
|
|
268
488
|
// Initialize behavior system
|
|
269
489
|
if (this.providers) {
|
|
270
490
|
this.behaviors = new BehaviorManager({
|
|
271
491
|
storePath: getBehaviorsPath(),
|
|
492
|
+
jobQueue: this.jobQueue,
|
|
272
493
|
executorDeps: {
|
|
273
494
|
getProvider: () => this.providers.getPrimaryProvider(),
|
|
274
495
|
sendToChannel: async (channelType, channelId, message) => {
|
|
275
496
|
this.logger.info('sendToChannel called', { channelType, channelId, hasChannels: !!this.channels });
|
|
497
|
+
let delivered = false;
|
|
276
498
|
// Always broadcast to webchat + persist
|
|
277
499
|
this.gateway.broadcast({
|
|
278
500
|
type: 'message',
|
|
279
501
|
payload: { role: 'assistant', content: message.content },
|
|
280
502
|
});
|
|
281
503
|
this.persistToWebchat(message.content);
|
|
504
|
+
delivered = true; // webchat broadcast is best-effort but counts
|
|
282
505
|
// Deliver to all connected external channels
|
|
283
506
|
if (this.channels) {
|
|
284
507
|
const connected = this.channels.getConnectedChannels();
|
|
@@ -290,12 +513,15 @@ export class Auxiora {
|
|
|
290
513
|
if (!targetId)
|
|
291
514
|
continue;
|
|
292
515
|
const result = await this.channels.send(ct, targetId, { content: message.content });
|
|
293
|
-
if (
|
|
516
|
+
if (result.success) {
|
|
517
|
+
delivered = true;
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
294
520
|
this.logger.warn('Channel delivery failed', { channel: ct, targetId, error: new Error(result.error ?? 'unknown') });
|
|
295
521
|
}
|
|
296
522
|
}
|
|
297
523
|
}
|
|
298
|
-
return { success:
|
|
524
|
+
return { success: delivered };
|
|
299
525
|
},
|
|
300
526
|
getSystemPrompt: () => this.systemPrompt,
|
|
301
527
|
executeWithTools: async (messages, systemPrompt) => {
|
|
@@ -779,6 +1005,10 @@ export class Auxiora {
|
|
|
779
1005
|
if (updates.agent) {
|
|
780
1006
|
await this.loadPersonality();
|
|
781
1007
|
}
|
|
1008
|
+
// Re-initialize channels when channel config changes
|
|
1009
|
+
if (updates.channels) {
|
|
1010
|
+
await this.reinitializeChannels();
|
|
1011
|
+
}
|
|
782
1012
|
},
|
|
783
1013
|
getAgentName: () => this.config.agent?.name ?? 'Auxiora',
|
|
784
1014
|
getAgentPronouns: () => this.config.agent?.pronouns ?? 'they/them',
|
|
@@ -877,6 +1107,115 @@ export class Auxiora {
|
|
|
877
1107
|
this.gateway.mountRouter('/dashboard', spaRouter);
|
|
878
1108
|
this.logger.info('Dashboard enabled at /dashboard');
|
|
879
1109
|
}
|
|
1110
|
+
// MCP management API routes
|
|
1111
|
+
this.gateway.mountRouter('/api/v1/mcp', this.createMcpRouter());
|
|
1112
|
+
// Personality management API routes
|
|
1113
|
+
if (this.architect) {
|
|
1114
|
+
const personalityRouter = this.createPersonalityRouter();
|
|
1115
|
+
this.gateway.mountRouter('/api/v1/personality', personalityRouter);
|
|
1116
|
+
}
|
|
1117
|
+
// Ambient agent API routes
|
|
1118
|
+
if (this.ambientEngine) {
|
|
1119
|
+
const ambientRouter = this.createAmbientRouter();
|
|
1120
|
+
this.gateway.mountRouter('/api/v1/ambient', ambientRouter);
|
|
1121
|
+
}
|
|
1122
|
+
// Deep research API routes
|
|
1123
|
+
const researchRouter = this.createResearchRouter();
|
|
1124
|
+
this.gateway.mountRouter('/api/v1/research', researchRouter);
|
|
1125
|
+
// Agent protocol API routes
|
|
1126
|
+
if (this.agentProtocol) {
|
|
1127
|
+
this.gateway.mountRouter('/api/v1/agent-protocol', this.createAgentProtocolRouter());
|
|
1128
|
+
}
|
|
1129
|
+
// Webhooks management API routes
|
|
1130
|
+
if (this.webhookManager) {
|
|
1131
|
+
this.gateway.mountRouter('/api/v1/webhooks', this.createWebhooksRouter());
|
|
1132
|
+
}
|
|
1133
|
+
// Consciousness API routes
|
|
1134
|
+
if (this.consciousness) {
|
|
1135
|
+
this.gateway.mountRouter('/api/v1/consciousness', this.createConsciousnessRouter());
|
|
1136
|
+
}
|
|
1137
|
+
// Voice API routes
|
|
1138
|
+
if (this.voiceManager) {
|
|
1139
|
+
this.gateway.mountRouter('/api/v1/voice', this.createVoiceRouter());
|
|
1140
|
+
}
|
|
1141
|
+
// Trust engine API routes
|
|
1142
|
+
if (this.trustEngine) {
|
|
1143
|
+
this.gateway.mountRouter('/api/v1/trust', this.createTrustRouter());
|
|
1144
|
+
}
|
|
1145
|
+
// Workflow API routes
|
|
1146
|
+
if (this.workflowEngine) {
|
|
1147
|
+
this.gateway.mountRouter('/api/v1/workflows', this.createWorkflowRouter());
|
|
1148
|
+
}
|
|
1149
|
+
// Connector API routes
|
|
1150
|
+
if (this.connectorRegistry) {
|
|
1151
|
+
this.gateway.mountRouter('/api/v1/connectors', this.createConnectorRouter());
|
|
1152
|
+
}
|
|
1153
|
+
// Self-update API routes
|
|
1154
|
+
if (this.updater) {
|
|
1155
|
+
this.gateway.mountRouter('/api/v1/update', this.createUpdateRouter());
|
|
1156
|
+
}
|
|
1157
|
+
// RAG API routes
|
|
1158
|
+
if (this.documentStore) {
|
|
1159
|
+
this.gateway.mountRouter('/api/v1/rag', this.createRagRouter());
|
|
1160
|
+
}
|
|
1161
|
+
// Evaluation API routes
|
|
1162
|
+
if (this.evalStore) {
|
|
1163
|
+
this.gateway.mountRouter('/api/v1/eval', this.createEvalRouter());
|
|
1164
|
+
}
|
|
1165
|
+
// Image generation API routes
|
|
1166
|
+
if (this.imageGenManager) {
|
|
1167
|
+
this.gateway.mountRouter('/api/v1/images', this.createImageRouter());
|
|
1168
|
+
}
|
|
1169
|
+
// Knowledge graph API routes
|
|
1170
|
+
if (this.knowledgeGraph) {
|
|
1171
|
+
this.gateway.mountRouter('/api/v1/knowledge', this.createKnowledgeRouter());
|
|
1172
|
+
}
|
|
1173
|
+
// Code interpreter API routes
|
|
1174
|
+
if (this.codeSessionManager) {
|
|
1175
|
+
this.gateway.mountRouter('/api/v1/code', this.createCodeRouter());
|
|
1176
|
+
}
|
|
1177
|
+
// Backup API routes
|
|
1178
|
+
if (this.backupManager) {
|
|
1179
|
+
this.gateway.mountRouter('/api/v1/backup', this.createBackupRouter());
|
|
1180
|
+
}
|
|
1181
|
+
// Approval queue API routes
|
|
1182
|
+
if (this.approvalQueue) {
|
|
1183
|
+
this.gateway.mountRouter('/api/v1/approvals', this.createApprovalQueueRouter());
|
|
1184
|
+
}
|
|
1185
|
+
// Vector store API routes
|
|
1186
|
+
if (this.vectorStore) {
|
|
1187
|
+
this.gateway.mountRouter('/api/v1/vectors', this.createVectorRouter());
|
|
1188
|
+
}
|
|
1189
|
+
// NL Automation API routes
|
|
1190
|
+
this.gateway.mountRouter('/api/v1/automation', this.createAutomationRouter());
|
|
1191
|
+
// Conversation branch API routes
|
|
1192
|
+
this.gateway.mountRouter('/api/v1/branches', this.createBranchRouter());
|
|
1193
|
+
// ReAct loop API routes
|
|
1194
|
+
this.gateway.mountRouter('/api/v1/react', this.createReactRouter());
|
|
1195
|
+
// A2A (Agent-to-Agent) API routes
|
|
1196
|
+
this.a2aAgentCard = new AgentCardBuilder()
|
|
1197
|
+
.setName('Auxiora')
|
|
1198
|
+
.setDescription('Auxiora AI assistant')
|
|
1199
|
+
.setUrl(`http://${this.config.gateway.host}:${this.config.gateway.port}`)
|
|
1200
|
+
.setVersion('1.0.0')
|
|
1201
|
+
.build();
|
|
1202
|
+
this.gateway.mountRouter('/api/v1/a2a', this.createA2ARouter());
|
|
1203
|
+
// Canvas API routes
|
|
1204
|
+
this.gateway.mountRouter('/api/v1/canvas', this.createCanvasRouter());
|
|
1205
|
+
// Sandbox API routes
|
|
1206
|
+
this.gateway.mountRouter('/api/v1/sandbox', this.createSandboxRouter());
|
|
1207
|
+
// Job queue status endpoint
|
|
1208
|
+
this.gateway.mountRouter('/api/v1/jobs', (() => {
|
|
1209
|
+
const jobsRouter = Router();
|
|
1210
|
+
jobsRouter.get('/status', (_req, res) => {
|
|
1211
|
+
if (!this.jobQueue) {
|
|
1212
|
+
res.status(503).json({ error: 'Job queue not initialized' });
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
res.json(this.jobQueue.getStats());
|
|
1216
|
+
});
|
|
1217
|
+
return jobsRouter;
|
|
1218
|
+
})());
|
|
880
1219
|
// Initialize plugin system (if enabled)
|
|
881
1220
|
if (this.config.plugins?.enabled !== false) {
|
|
882
1221
|
const pluginsDir = this.config.plugins?.dir || undefined;
|
|
@@ -954,12 +1293,90 @@ export class Auxiora {
|
|
|
954
1293
|
await this.agentDirectory.register(agentId, agentName, agentKeys.publicKey, `http://${agentHost}/api/v1/agent-protocol`);
|
|
955
1294
|
this.agentProtocol = new AgentProtocol(agentId, agentSigner, this.agentDirectory);
|
|
956
1295
|
this.logger.info('Agent protocol initialized');
|
|
1296
|
+
// Initialize self-update system
|
|
1297
|
+
try {
|
|
1298
|
+
this.installationDetector = new InstallationDetector();
|
|
1299
|
+
this.versionChecker = new VersionChecker('auxiora', 'auxiora');
|
|
1300
|
+
const healthChecker = new HealthChecker(`http://${agentHost}`);
|
|
1301
|
+
const strategies = createStrategyMap();
|
|
1302
|
+
this.updater = new Updater({
|
|
1303
|
+
detector: this.installationDetector,
|
|
1304
|
+
versionChecker: this.versionChecker,
|
|
1305
|
+
healthChecker,
|
|
1306
|
+
strategies,
|
|
1307
|
+
});
|
|
1308
|
+
// Recover from any incomplete previous update
|
|
1309
|
+
const recovery = await this.updater.recoverIfNeeded();
|
|
1310
|
+
if (recovery) {
|
|
1311
|
+
this.logger.warn('Recovered from incomplete update', {
|
|
1312
|
+
previousVersion: recovery.previousVersion,
|
|
1313
|
+
targetVersion: recovery.newVersion,
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
this.logger.info('Self-update system initialized');
|
|
1317
|
+
}
|
|
1318
|
+
catch (err) {
|
|
1319
|
+
this.logger.warn('Failed to initialize self-update system', {
|
|
1320
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
957
1323
|
// [P15] Initialize ambient intelligence
|
|
958
|
-
|
|
1324
|
+
// Restore persisted patterns from vault, or start fresh
|
|
1325
|
+
try {
|
|
1326
|
+
const storedPatterns = this.vault.get('ambient:patterns');
|
|
1327
|
+
this.ambientEngine = storedPatterns
|
|
1328
|
+
? AmbientPatternEngine.deserialize(storedPatterns)
|
|
1329
|
+
: new AmbientPatternEngine();
|
|
1330
|
+
}
|
|
1331
|
+
catch {
|
|
1332
|
+
this.ambientEngine = new AmbientPatternEngine();
|
|
1333
|
+
}
|
|
959
1334
|
this.ambientNotifications = new QuietNotificationManager();
|
|
960
1335
|
this.briefingGenerator = new BriefingGenerator();
|
|
961
1336
|
this.anticipationEngine = new AnticipationEngine();
|
|
1337
|
+
this.ambientAwarenessCollector = new AmbientAwarenessCollector();
|
|
1338
|
+
if (this.jobQueue) {
|
|
1339
|
+
this.jobQueue.enqueue('ambient-flush', {}, { scheduledAt: Date.now() + 5 * 60 * 1000 });
|
|
1340
|
+
}
|
|
962
1341
|
this.logger.info('Ambient intelligence initialized');
|
|
1342
|
+
// Initialize RAG document store
|
|
1343
|
+
this.documentStore = new DocumentStore();
|
|
1344
|
+
this.contextBuilder = new ContextBuilder();
|
|
1345
|
+
this.logger.info('RAG document store initialized');
|
|
1346
|
+
// Initialize image generation (conditional on provider keys)
|
|
1347
|
+
{
|
|
1348
|
+
let openaiKey;
|
|
1349
|
+
let replicateToken;
|
|
1350
|
+
try {
|
|
1351
|
+
openaiKey = this.vault.get('OPENAI_API_KEY');
|
|
1352
|
+
}
|
|
1353
|
+
catch { /* vault locked */ }
|
|
1354
|
+
try {
|
|
1355
|
+
replicateToken = this.vault.get('REPLICATE_API_TOKEN');
|
|
1356
|
+
}
|
|
1357
|
+
catch { /* vault locked */ }
|
|
1358
|
+
if (openaiKey || replicateToken) {
|
|
1359
|
+
this.imageGenManager = new ImageGenManager();
|
|
1360
|
+
if (openaiKey) {
|
|
1361
|
+
this.imageGenManager.registerProvider(new OpenAIImageProvider(openaiKey));
|
|
1362
|
+
}
|
|
1363
|
+
if (replicateToken) {
|
|
1364
|
+
this.imageGenManager.registerProvider(new ReplicateImageProvider(replicateToken));
|
|
1365
|
+
}
|
|
1366
|
+
this.logger.info(`Image generation initialized with providers: ${this.imageGenManager.listProviders().join(', ')}`);
|
|
1367
|
+
}
|
|
1368
|
+
else {
|
|
1369
|
+
this.logger.info('Image generation skipped: no OPENAI_API_KEY or REPLICATE_API_TOKEN in vault');
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
// Initialize knowledge graph
|
|
1373
|
+
this.knowledgeGraph = new GraphStore();
|
|
1374
|
+
this.entityLinker = new EntityLinker();
|
|
1375
|
+
this.logger.info('Knowledge graph initialized');
|
|
1376
|
+
// Initialize code interpreter
|
|
1377
|
+
this.codeExecutor = new CodeExecutor();
|
|
1378
|
+
this.codeSessionManager = new CodeSessionManager(this.codeExecutor);
|
|
1379
|
+
this.logger.info('Code interpreter initialized');
|
|
963
1380
|
// Initialize notification orchestrator
|
|
964
1381
|
this.notificationHub = new NotificationHub();
|
|
965
1382
|
this.dndManager = new DoNotDisturbManager();
|
|
@@ -1060,6 +1477,38 @@ export class Auxiora {
|
|
|
1060
1477
|
this.ambientScheduler.start();
|
|
1061
1478
|
this.logger.info('Ambient scheduler started');
|
|
1062
1479
|
}
|
|
1480
|
+
// Run pattern detection and persist to vault every 5 minutes
|
|
1481
|
+
const PATTERN_DETECT_INTERVAL = 5 * 60 * 1000;
|
|
1482
|
+
this.ambientDetectTimer = setInterval(async () => {
|
|
1483
|
+
// Poll triggers and route events
|
|
1484
|
+
if (this.triggerManager) {
|
|
1485
|
+
try {
|
|
1486
|
+
const events = await this.triggerManager.pollAll();
|
|
1487
|
+
await this.processEventTriggers(events);
|
|
1488
|
+
}
|
|
1489
|
+
catch { /* poll failure */ }
|
|
1490
|
+
}
|
|
1491
|
+
// Detect patterns and persist
|
|
1492
|
+
if (this.ambientEngine) {
|
|
1493
|
+
this.ambientEngine.detectPatterns();
|
|
1494
|
+
try {
|
|
1495
|
+
await this.vault.add('ambient:patterns', this.ambientEngine.serialize());
|
|
1496
|
+
}
|
|
1497
|
+
catch { /* vault locked */ }
|
|
1498
|
+
// Update awareness collector
|
|
1499
|
+
if (this.ambientAwarenessCollector) {
|
|
1500
|
+
this.ambientAwarenessCollector.updatePatterns(this.ambientEngine.getPatterns());
|
|
1501
|
+
if (this.anticipationEngine) {
|
|
1502
|
+
const anticipations = this.anticipationEngine.generateAnticipations(this.ambientEngine.getPatterns());
|
|
1503
|
+
this.ambientAwarenessCollector.updateAnticipations(anticipations);
|
|
1504
|
+
}
|
|
1505
|
+
this.ambientAwarenessCollector.updateActivity({
|
|
1506
|
+
eventRate: this.ambientEngine.getEventCount(),
|
|
1507
|
+
activeBehaviors: 0,
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}, PATTERN_DETECT_INTERVAL);
|
|
1063
1512
|
// [P15] Initialize conversation engine
|
|
1064
1513
|
this.conversationEngine = new ConversationEngine();
|
|
1065
1514
|
this.logger.info('Conversation engine initialized');
|
|
@@ -1103,12 +1552,13 @@ export class Auxiora {
|
|
|
1103
1552
|
toolCount: p.toolCount, behaviorNames: p.behaviorNames,
|
|
1104
1553
|
})),
|
|
1105
1554
|
getFeatures: () => ({
|
|
1106
|
-
behaviors: this.
|
|
1107
|
-
browser: this.
|
|
1108
|
-
voice: !!this.
|
|
1109
|
-
webhooks: !!this.
|
|
1110
|
-
plugins: !!this.
|
|
1111
|
-
memory: this.
|
|
1555
|
+
behaviors: !!this.behaviors,
|
|
1556
|
+
browser: !!this.browserManager,
|
|
1557
|
+
voice: !!this.voiceManager,
|
|
1558
|
+
webhooks: !!this.webhookManager,
|
|
1559
|
+
plugins: !!(this.pluginLoader && this.pluginLoader.listPlugins().length > 0),
|
|
1560
|
+
memory: !!this.memoryStore,
|
|
1561
|
+
research: !!this.researchEngine,
|
|
1112
1562
|
}),
|
|
1113
1563
|
getAuditEntries: async (limit) => {
|
|
1114
1564
|
const al = getAuditLogger();
|
|
@@ -1121,10 +1571,51 @@ export class Auxiora {
|
|
|
1121
1571
|
const initialHealth = { overall: 'healthy', subsystems: [], issues: [], lastCheck: new Date().toISOString() };
|
|
1122
1572
|
this.capabilityPromptFragment = generatePromptFragment(this.capabilityCatalog.getCatalog(), initialHealth, this.getSelfAwarenessContext());
|
|
1123
1573
|
const autoFixActions = {
|
|
1124
|
-
reconnectChannel: async () =>
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1574
|
+
reconnectChannel: async (type) => {
|
|
1575
|
+
if (!this.channels)
|
|
1576
|
+
return false;
|
|
1577
|
+
try {
|
|
1578
|
+
await this.channels.disconnect(type);
|
|
1579
|
+
await this.channels.connect(type);
|
|
1580
|
+
this.logger.info('Auto-fix: reconnected channel', { type });
|
|
1581
|
+
return true;
|
|
1582
|
+
}
|
|
1583
|
+
catch (err) {
|
|
1584
|
+
this.logger.warn('Auto-fix: channel reconnect failed', { type, error: err instanceof Error ? err : new Error(String(err)) });
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
},
|
|
1588
|
+
restartBehavior: async (id) => {
|
|
1589
|
+
if (!this.behaviors)
|
|
1590
|
+
return false;
|
|
1591
|
+
try {
|
|
1592
|
+
const result = await this.behaviors.update(id, { status: 'active' });
|
|
1593
|
+
if (!result)
|
|
1594
|
+
return false;
|
|
1595
|
+
this.logger.info('Auto-fix: restarted behavior', { id });
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
catch (err) {
|
|
1599
|
+
this.logger.warn('Auto-fix: behavior restart failed', { id, error: err instanceof Error ? err : new Error(String(err)) });
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1602
|
+
},
|
|
1603
|
+
switchToFallbackProvider: async () => {
|
|
1604
|
+
const fallbackName = this.config.provider.fallback;
|
|
1605
|
+
if (!fallbackName)
|
|
1606
|
+
return false;
|
|
1607
|
+
const fallback = this.providers.getFallbackProvider();
|
|
1608
|
+
if (!fallback)
|
|
1609
|
+
return false;
|
|
1610
|
+
try {
|
|
1611
|
+
this.providers.setPrimary(fallbackName);
|
|
1612
|
+
this.logger.info('Auto-fix: switched to fallback provider', { name: fallbackName });
|
|
1613
|
+
return true;
|
|
1614
|
+
}
|
|
1615
|
+
catch (err) {
|
|
1616
|
+
this.logger.warn('Auto-fix: provider switch failed', { error: err instanceof Error ? err : new Error(String(err)) });
|
|
1617
|
+
return false;
|
|
1618
|
+
}
|
|
1128
1619
|
},
|
|
1129
1620
|
};
|
|
1130
1621
|
this.healthMonitor = new HealthMonitorImpl(introspectionSources, autoFixActions);
|
|
@@ -1503,6 +1994,38 @@ export class Auxiora {
|
|
|
1503
1994
|
this.pluginLoader.setChannelManager(this.channels);
|
|
1504
1995
|
}
|
|
1505
1996
|
}
|
|
1997
|
+
async reinitializeChannels() {
|
|
1998
|
+
// Disconnect existing channels gracefully
|
|
1999
|
+
if (this.channels) {
|
|
2000
|
+
try {
|
|
2001
|
+
await this.channels.disconnectAll();
|
|
2002
|
+
}
|
|
2003
|
+
catch (error) {
|
|
2004
|
+
this.logger.warn('Error disconnecting channels during reinit', {
|
|
2005
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
this.channels = undefined;
|
|
2009
|
+
}
|
|
2010
|
+
// Re-initialize with updated config and vault
|
|
2011
|
+
await this.initializeChannels();
|
|
2012
|
+
// Connect the newly initialized channels (cast needed: initializeChannels may set this.channels)
|
|
2013
|
+
const channels = this.channels;
|
|
2014
|
+
if (channels) {
|
|
2015
|
+
try {
|
|
2016
|
+
await channels.connectAll();
|
|
2017
|
+
const connected = channels.getConnectedChannels();
|
|
2018
|
+
if (connected.length > 0) {
|
|
2019
|
+
this.logger.info(`Channels reconnected: ${connected.join(', ')}`);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
catch (error) {
|
|
2023
|
+
this.logger.warn('Some channels failed to connect during reinit', {
|
|
2024
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
1506
2029
|
buildIdentityPreamble(agent) {
|
|
1507
2030
|
const lines = ['# Agent Identity'];
|
|
1508
2031
|
lines.push(`You are ${agent.name} (${agent.pronouns}).`);
|
|
@@ -1676,6 +2199,9 @@ export class Auxiora {
|
|
|
1676
2199
|
if (this.architectAwarenessCollector) {
|
|
1677
2200
|
collectors.push(this.architectAwarenessCollector);
|
|
1678
2201
|
}
|
|
2202
|
+
if (this.ambientAwarenessCollector) {
|
|
2203
|
+
collectors.push(this.ambientAwarenessCollector);
|
|
2204
|
+
}
|
|
1679
2205
|
this.selfAwarenessAssembler = new SelfAwarenessAssembler(collectors, {
|
|
1680
2206
|
tokenBudget: this.config.selfAwareness.tokenBudget ?? 500,
|
|
1681
2207
|
});
|
|
@@ -1703,30 +2229,37 @@ export class Auxiora {
|
|
|
1703
2229
|
personalityEngine: this.config.agent.personality ?? 'standard',
|
|
1704
2230
|
};
|
|
1705
2231
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
2232
|
+
async getCachedSelfModel() {
|
|
2233
|
+
if (!this.consciousness)
|
|
2234
|
+
return null;
|
|
2235
|
+
const now = Date.now();
|
|
2236
|
+
if (this.selfModelCache && (now - this.selfModelCache.cachedAt) < Auxiora.MODEL_CACHE_TTL) {
|
|
2237
|
+
return this.selfModelCache.snapshot;
|
|
2238
|
+
}
|
|
2239
|
+
try {
|
|
2240
|
+
const snapshot = await this.consciousness.model.synthesize();
|
|
2241
|
+
this.selfModelCache = { snapshot, cachedAt: now };
|
|
2242
|
+
return snapshot;
|
|
2243
|
+
}
|
|
2244
|
+
catch {
|
|
2245
|
+
return this.selfModelCache?.snapshot ?? null;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
getCachedUserModel() {
|
|
1708
2249
|
if (!this.architect)
|
|
1709
|
-
return
|
|
1710
|
-
const
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
this.architectBridge.afterPrompt(output.detectedContext, output.emotionalTrajectory, output.escalationAlert, chatId);
|
|
2250
|
+
return null;
|
|
2251
|
+
const now = Date.now();
|
|
2252
|
+
if (this.userModelCache && (now - this.userModelCache.cachedAt) < Auxiora.MODEL_CACHE_TTL) {
|
|
2253
|
+
return this.userModelCache.model;
|
|
1714
2254
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
2255
|
+
try {
|
|
2256
|
+
const model = this.architect.getUserModel();
|
|
2257
|
+
this.userModelCache = { model, cachedAt: now };
|
|
2258
|
+
return model;
|
|
2259
|
+
}
|
|
2260
|
+
catch {
|
|
2261
|
+
return this.userModelCache?.model ?? null;
|
|
1719
2262
|
}
|
|
1720
|
-
return {
|
|
1721
|
-
prompt: prompt + '\n\n' + output.contextModifier,
|
|
1722
|
-
architectMeta: {
|
|
1723
|
-
detectedContext: output.detectedContext,
|
|
1724
|
-
activeTraits: output.activeTraits,
|
|
1725
|
-
traitWeights,
|
|
1726
|
-
recommendation: output.recommendation,
|
|
1727
|
-
escalationAlert: output.escalationAlert,
|
|
1728
|
-
},
|
|
1729
|
-
};
|
|
1730
2263
|
}
|
|
1731
2264
|
async initializeModes() {
|
|
1732
2265
|
if (this.config.modes?.enabled === false)
|
|
@@ -1750,19 +2283,62 @@ export class Auxiora {
|
|
|
1750
2283
|
}
|
|
1751
2284
|
return state;
|
|
1752
2285
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2286
|
+
buildEnrichmentPipeline() {
|
|
2287
|
+
this.enrichmentPipeline = new EnrichmentPipeline();
|
|
2288
|
+
// Stage 1: Memory (order 100)
|
|
2289
|
+
if (this.memoryStore && this.memoryRetriever) {
|
|
2290
|
+
this.enrichmentPipeline.addStage(new MemoryStage(this.memoryStore, this.memoryRetriever));
|
|
2291
|
+
}
|
|
2292
|
+
// Stage 2: Mode detection + security (order 200)
|
|
2293
|
+
if (this.modeDetector && this.promptAssembler) {
|
|
2294
|
+
this.enrichmentPipeline.addStage(new ModeStage({
|
|
2295
|
+
detector: this.modeDetector,
|
|
2296
|
+
assembler: this.promptAssembler,
|
|
2297
|
+
securityFloor: this.securityFloor,
|
|
2298
|
+
userPreferences: this.userPreferences,
|
|
2299
|
+
getModeState: (sessionId) => this.getSessionModeState(sessionId),
|
|
2300
|
+
}));
|
|
2301
|
+
}
|
|
2302
|
+
// Stage 3: Architect (order 300)
|
|
2303
|
+
if (this.architect) {
|
|
2304
|
+
this.enrichmentPipeline.addStage(new ArchitectStage(this.architect, this.architectBridge ?? undefined, this.architectAwarenessCollector ?? undefined, () => this.getCachedSelfModel(), () => this.getCachedUserModel()));
|
|
2305
|
+
}
|
|
2306
|
+
// Stage 4: Self-awareness (order 400)
|
|
2307
|
+
if (this.selfAwarenessAssembler) {
|
|
2308
|
+
this.enrichmentPipeline.addStage(new SelfAwarenessStage(this.selfAwarenessAssembler));
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
GUARDRAIL_BLOCK_MESSAGE = 'I\'m not able to process that request. If you believe this is an error, please rephrase your message.';
|
|
2312
|
+
buildModelIdentityFragment(provider, model) {
|
|
2313
|
+
const activeModel = model ?? provider.defaultModel;
|
|
2314
|
+
const caps = provider.metadata.models[activeModel];
|
|
2315
|
+
return '\n\n[Model Identity]\n'
|
|
2316
|
+
+ `You are running as ${activeModel} via ${provider.metadata.displayName}.`
|
|
2317
|
+
+ (caps ? ` Context window: ${caps.maxContextTokens.toLocaleString()} tokens.` : '')
|
|
2318
|
+
+ (caps?.supportsVision ? ' You have vision capabilities.' : '')
|
|
2319
|
+
+ ` Today's date: ${new Date().toISOString().slice(0, 10)}.`;
|
|
2320
|
+
}
|
|
2321
|
+
checkInputGuardrails(content) {
|
|
2322
|
+
if (!this.guardrailPipeline)
|
|
2323
|
+
return null;
|
|
2324
|
+
const result = this.guardrailPipeline.scanInput(content);
|
|
2325
|
+
if (result.action !== 'allow') {
|
|
2326
|
+
this.logger.debug('Input guardrail triggered', { action: result.action, threatCount: result.threats.length });
|
|
2327
|
+
}
|
|
2328
|
+
return result;
|
|
2329
|
+
}
|
|
2330
|
+
checkOutputGuardrails(response) {
|
|
2331
|
+
if (!this.guardrailPipeline || this.config.guardrails?.scanOutput === false || !response) {
|
|
2332
|
+
return { response, wasModified: false, action: 'allow' };
|
|
2333
|
+
}
|
|
2334
|
+
const result = this.guardrailPipeline.scanOutput(response);
|
|
2335
|
+
if (result.action === 'block') {
|
|
2336
|
+
return { response: this.GUARDRAIL_BLOCK_MESSAGE, wasModified: true, action: 'block' };
|
|
1764
2337
|
}
|
|
1765
|
-
|
|
2338
|
+
if (result.action === 'redact' && result.redactedContent) {
|
|
2339
|
+
return { response: result.redactedContent, wasModified: true, action: 'redact' };
|
|
2340
|
+
}
|
|
2341
|
+
return { response, wasModified: false, action: result.action };
|
|
1766
2342
|
}
|
|
1767
2343
|
async handleMessage(client, message) {
|
|
1768
2344
|
const { id: requestId, payload } = message;
|
|
@@ -1774,6 +2350,55 @@ export class Auxiora {
|
|
|
1774
2350
|
}
|
|
1775
2351
|
return;
|
|
1776
2352
|
}
|
|
2353
|
+
// Handle message feedback (thumbs up/down for Architect learning)
|
|
2354
|
+
if (message.type === 'message_feedback') {
|
|
2355
|
+
const fbPayload = payload;
|
|
2356
|
+
if (this.architect && fbPayload?.messageId && fbPayload?.rating) {
|
|
2357
|
+
// Look up the message to get architectDomain from metadata
|
|
2358
|
+
let domain = 'general';
|
|
2359
|
+
if (fbPayload.sessionId) {
|
|
2360
|
+
const msgs = this.sessions.getMessages(fbPayload.sessionId);
|
|
2361
|
+
const msg = msgs.find((m) => m.id === fbPayload.messageId);
|
|
2362
|
+
if (msg?.metadata?.architectDomain) {
|
|
2363
|
+
domain = msg.metadata.architectDomain;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
const mapped = fbPayload.rating === 'up' ? 'helpful' : 'off_target';
|
|
2367
|
+
await this.architect.recordFeedback({
|
|
2368
|
+
domain: domain,
|
|
2369
|
+
rating: mapped,
|
|
2370
|
+
note: fbPayload.note,
|
|
2371
|
+
});
|
|
2372
|
+
audit('personality.feedback', {
|
|
2373
|
+
sessionId: fbPayload.sessionId,
|
|
2374
|
+
messageId: fbPayload.messageId,
|
|
2375
|
+
rating: fbPayload.rating,
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
// Handle deep research job requests
|
|
2381
|
+
if (message.type === 'start_research') {
|
|
2382
|
+
const researchPayload = payload;
|
|
2383
|
+
if (researchPayload?.question) {
|
|
2384
|
+
const job = {
|
|
2385
|
+
id: crypto.randomUUID(),
|
|
2386
|
+
question: researchPayload.question,
|
|
2387
|
+
depth: researchPayload.depth ?? 'deep',
|
|
2388
|
+
status: 'planning',
|
|
2389
|
+
createdAt: Date.now(),
|
|
2390
|
+
progress: [],
|
|
2391
|
+
};
|
|
2392
|
+
this.researchJobs.set(job.id, job);
|
|
2393
|
+
audit('research.started', { jobId: job.id, question: job.question, depth: job.depth });
|
|
2394
|
+
this.sendToClient(client, { type: 'research_started', id: requestId, payload: { jobId: job.id } });
|
|
2395
|
+
this.runResearchJob(job, client).catch((err) => {
|
|
2396
|
+
job.status = 'failed';
|
|
2397
|
+
this.logger.error('Research job failed', { error: err instanceof Error ? err : new Error(String(err)), jobId: job.id });
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
1777
2402
|
const msgPayload = payload;
|
|
1778
2403
|
const content = msgPayload?.content;
|
|
1779
2404
|
const modelOverride = msgPayload?.model;
|
|
@@ -1788,6 +2413,28 @@ export class Auxiora {
|
|
|
1788
2413
|
});
|
|
1789
2414
|
return;
|
|
1790
2415
|
}
|
|
2416
|
+
// ── Research intent detection ──────────────────────────────────
|
|
2417
|
+
const researchIntent = this.intentDetector.detect(content);
|
|
2418
|
+
if (researchIntent.score >= 0.6) {
|
|
2419
|
+
this.sendToClient(client, { type: 'research_suggestion', id: requestId, payload: researchIntent });
|
|
2420
|
+
}
|
|
2421
|
+
// ── Guardrail input scan ──────────────────────────────────────
|
|
2422
|
+
const inputScan = this.checkInputGuardrails(content);
|
|
2423
|
+
if (inputScan && inputScan.action === 'block') {
|
|
2424
|
+
audit('guardrail.triggered', {
|
|
2425
|
+
action: 'block',
|
|
2426
|
+
direction: 'input',
|
|
2427
|
+
threatCount: inputScan.threats.length,
|
|
2428
|
+
channelType: 'webchat',
|
|
2429
|
+
});
|
|
2430
|
+
this.sendToClient(client, {
|
|
2431
|
+
type: 'message',
|
|
2432
|
+
id: requestId,
|
|
2433
|
+
payload: { role: 'assistant', content: this.GUARDRAIL_BLOCK_MESSAGE },
|
|
2434
|
+
});
|
|
2435
|
+
this.sendToClient(client, { type: 'done', id: requestId, payload: {} });
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
1791
2438
|
// Handle commands
|
|
1792
2439
|
if (content.startsWith('/')) {
|
|
1793
2440
|
await this.handleCommand(client, content, requestId);
|
|
@@ -1816,8 +2463,29 @@ export class Auxiora {
|
|
|
1816
2463
|
senderId: client.senderId,
|
|
1817
2464
|
});
|
|
1818
2465
|
}
|
|
2466
|
+
// Apply redaction if guardrails flagged PII
|
|
2467
|
+
let processedContent = content;
|
|
2468
|
+
if (inputScan?.action === 'redact' && inputScan.redactedContent) {
|
|
2469
|
+
processedContent = inputScan.redactedContent;
|
|
2470
|
+
audit('guardrail.triggered', {
|
|
2471
|
+
action: 'redact',
|
|
2472
|
+
direction: 'input',
|
|
2473
|
+
threatCount: inputScan.threats.length,
|
|
2474
|
+
channelType: 'webchat',
|
|
2475
|
+
sessionId: session.id,
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
else if (inputScan?.action === 'warn') {
|
|
2479
|
+
audit('guardrail.triggered', {
|
|
2480
|
+
action: 'warn',
|
|
2481
|
+
direction: 'input',
|
|
2482
|
+
threatCount: inputScan.threats.length,
|
|
2483
|
+
channelType: 'webchat',
|
|
2484
|
+
sessionId: session.id,
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
1819
2487
|
// Add user message
|
|
1820
|
-
await this.sessions.addMessage(session.id, 'user',
|
|
2488
|
+
await this.sessions.addMessage(session.id, 'user', processedContent);
|
|
1821
2489
|
// Check if providers are available
|
|
1822
2490
|
if (!this.providers) {
|
|
1823
2491
|
this.sendToClient(client, {
|
|
@@ -1846,60 +2514,30 @@ export class Auxiora {
|
|
|
1846
2514
|
? chatPersonality === 'the-architect'
|
|
1847
2515
|
: this.config.agent.personality === 'the-architect';
|
|
1848
2516
|
const basePrompt = useArchitect ? this.architectPrompt : this.standardPrompt;
|
|
1849
|
-
// Build enriched prompt
|
|
2517
|
+
// Build enriched prompt through pipeline
|
|
1850
2518
|
let enrichedPrompt = basePrompt;
|
|
1851
|
-
let
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
const modeState = this.getSessionModeState(session.id);
|
|
1858
|
-
// Security context check — BEFORE mode detection
|
|
1859
|
-
if (this.securityFloor) {
|
|
1860
|
-
const securityContext = this.securityFloor.detectSecurityContext({ userMessage: content });
|
|
1861
|
-
if (securityContext.active) {
|
|
1862
|
-
// Suspend current mode and use security floor prompt
|
|
1863
|
-
modeState.suspendedMode = modeState.activeMode;
|
|
1864
|
-
enrichedPrompt = this.promptAssembler.enrichForSecurityContext(securityContext, this.securityFloor, memorySection);
|
|
1865
|
-
}
|
|
1866
|
-
else if (modeState.suspendedMode) {
|
|
1867
|
-
// Restore suspended mode
|
|
1868
|
-
modeState.activeMode = modeState.suspendedMode;
|
|
1869
|
-
delete modeState.suspendedMode;
|
|
1870
|
-
enrichedPrompt = this.promptAssembler.enrichForMessage(modeState, memorySection, this.userPreferences, undefined, 'webchat');
|
|
1871
|
-
}
|
|
1872
|
-
else {
|
|
1873
|
-
// Normal mode detection
|
|
1874
|
-
enrichedPrompt = this.buildModeEnrichedPrompt(content, modeState, memorySection, 'webchat');
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
else {
|
|
1878
|
-
// No security floor — normal mode detection
|
|
1879
|
-
enrichedPrompt = this.buildModeEnrichedPrompt(content, modeState, memorySection, 'webchat');
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
else if (memorySection) {
|
|
1883
|
-
enrichedPrompt = basePrompt + memorySection;
|
|
2519
|
+
let architectResult = { prompt: basePrompt };
|
|
2520
|
+
// Reset Architect conversation state for new chats
|
|
2521
|
+
if (useArchitect && this.architect && chatId && !this.architectResetChats.has(chatId)) {
|
|
2522
|
+
this.architectResetChats.add(chatId);
|
|
2523
|
+
this.architect.resetConversation();
|
|
2524
|
+
audit('personality.reset', { sessionId: session.id, chatId });
|
|
1884
2525
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
if (this.selfAwarenessAssembler) {
|
|
1892
|
-
const awarenessContext = {
|
|
1893
|
-
userId: client.senderId ?? 'anonymous',
|
|
1894
|
-
sessionId: session.id,
|
|
2526
|
+
if (this.enrichmentPipeline) {
|
|
2527
|
+
const enrichCtx = {
|
|
2528
|
+
basePrompt,
|
|
2529
|
+
userMessage: processedContent,
|
|
2530
|
+
history: contextMessages,
|
|
2531
|
+
channelType: 'webchat',
|
|
1895
2532
|
chatId: chatId ?? session.id,
|
|
1896
|
-
|
|
1897
|
-
|
|
2533
|
+
sessionId: session.id,
|
|
2534
|
+
userId: client.senderId ?? 'anonymous',
|
|
2535
|
+
toolsUsed: this.lastToolsUsed.get(session.id) ?? [],
|
|
2536
|
+
config: this.config,
|
|
1898
2537
|
};
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
}
|
|
2538
|
+
const result = await this.enrichmentPipeline.run(enrichCtx);
|
|
2539
|
+
enrichedPrompt = result.prompt;
|
|
2540
|
+
architectResult = { prompt: enrichedPrompt, architectMeta: result.metadata.architect };
|
|
1903
2541
|
}
|
|
1904
2542
|
// Route to best model for this message
|
|
1905
2543
|
let provider;
|
|
@@ -1910,7 +2548,7 @@ export class Auxiora {
|
|
|
1910
2548
|
}
|
|
1911
2549
|
else if (this.modelRouter && this.config.routing?.enabled !== false) {
|
|
1912
2550
|
try {
|
|
1913
|
-
routingResult = this.modelRouter.route(
|
|
2551
|
+
routingResult = this.modelRouter.route(processedContent, { hasImages: false });
|
|
1914
2552
|
provider = this.providers.getProvider(routingResult.selection.provider);
|
|
1915
2553
|
}
|
|
1916
2554
|
catch {
|
|
@@ -1920,30 +2558,89 @@ export class Auxiora {
|
|
|
1920
2558
|
else {
|
|
1921
2559
|
provider = this.providers.getPrimaryProvider();
|
|
1922
2560
|
}
|
|
2561
|
+
// Inject model identity so the AI knows what it's running on
|
|
2562
|
+
enrichedPrompt += this.buildModelIdentityFragment(provider, routingResult?.selection.model ?? modelOverride);
|
|
1923
2563
|
// Execute streaming AI call with tool follow-up loop
|
|
2564
|
+
const processingStartTime = Date.now();
|
|
1924
2565
|
const fallbackCandidates = this.providers.resolveFallbackCandidates();
|
|
2566
|
+
const toolsUsed = [];
|
|
2567
|
+
let streamChunkCount = 0;
|
|
1925
2568
|
const { response: fullResponse, usage } = await this.executeWithTools(session.id, chatMessages, enrichedPrompt, provider, (type, data) => {
|
|
1926
2569
|
if (type === 'text') {
|
|
2570
|
+
streamChunkCount++;
|
|
1927
2571
|
this.sendToClient(client, { type: 'chunk', id: requestId, payload: { content: data } });
|
|
1928
2572
|
}
|
|
1929
2573
|
else if (type === 'thinking') {
|
|
1930
2574
|
this.sendToClient(client, { type: 'thinking', id: requestId, payload: { content: data } });
|
|
1931
2575
|
}
|
|
1932
2576
|
else if (type === 'tool_use') {
|
|
2577
|
+
toolsUsed.push({ name: data?.name ?? 'unknown', success: true });
|
|
1933
2578
|
this.sendToClient(client, { type: 'tool_use', id: requestId, payload: data });
|
|
1934
2579
|
}
|
|
1935
2580
|
else if (type === 'tool_result') {
|
|
2581
|
+
// Update last tool's success based on result
|
|
2582
|
+
if (toolsUsed.length > 0 && data?.error) {
|
|
2583
|
+
toolsUsed[toolsUsed.length - 1].success = false;
|
|
2584
|
+
}
|
|
1936
2585
|
this.sendToClient(client, { type: 'tool_result', id: requestId, payload: data });
|
|
1937
2586
|
}
|
|
1938
2587
|
else if (type === 'status') {
|
|
1939
2588
|
this.sendToClient(client, { type: 'status', id: requestId, payload: data });
|
|
1940
2589
|
}
|
|
1941
2590
|
}, { tools, fallbackCandidates });
|
|
2591
|
+
// Feed tool usage to awareness collector
|
|
2592
|
+
if (this.architectAwarenessCollector && toolsUsed.length > 0) {
|
|
2593
|
+
this.architectAwarenessCollector.updateToolContext(toolsUsed);
|
|
2594
|
+
}
|
|
2595
|
+
// Store tools for next turn's enrichment context
|
|
2596
|
+
this.lastToolsUsed.set(session.id, toolsUsed);
|
|
2597
|
+
// ── Guardrail output scan ─────────────────────────────────────
|
|
2598
|
+
const outputScan = this.checkOutputGuardrails(fullResponse);
|
|
2599
|
+
const finalResponse = outputScan.response;
|
|
2600
|
+
if (outputScan.wasModified) {
|
|
2601
|
+
audit('guardrail.triggered', {
|
|
2602
|
+
action: outputScan.action,
|
|
2603
|
+
direction: 'output',
|
|
2604
|
+
channelType: 'webchat',
|
|
2605
|
+
sessionId: session.id,
|
|
2606
|
+
});
|
|
2607
|
+
// Send correction since chunks were already streamed
|
|
2608
|
+
this.sendToClient(client, {
|
|
2609
|
+
type: 'guardrail_correction',
|
|
2610
|
+
id: requestId,
|
|
2611
|
+
payload: { content: finalResponse },
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
// Collect transparency metadata (best-effort)
|
|
2615
|
+
let transparencyMeta;
|
|
2616
|
+
try {
|
|
2617
|
+
const modelId = routingResult?.selection.model ?? modelOverride ?? provider.defaultModel;
|
|
2618
|
+
const caps = provider.metadata.models[modelId];
|
|
2619
|
+
if (caps) {
|
|
2620
|
+
transparencyMeta = collectTransparencyMeta({
|
|
2621
|
+
enrichment: this.enrichmentPipeline
|
|
2622
|
+
? { prompt: enrichedPrompt, metadata: { architect: architectResult.architectMeta, stages: architectResult.stages ?? [] } }
|
|
2623
|
+
: { prompt: enrichedPrompt, metadata: { stages: [] } },
|
|
2624
|
+
completion: { content: finalResponse, usage, model: modelId, finishReason: 'stop', toolUse: toolsUsed.map(t => ({ name: t.name })) },
|
|
2625
|
+
capabilities: { costPer1kInput: caps.costPer1kInput, costPer1kOutput: caps.costPer1kOutput },
|
|
2626
|
+
providerName: provider.name,
|
|
2627
|
+
awarenessSignals: [],
|
|
2628
|
+
responseText: finalResponse,
|
|
2629
|
+
processingStartTime,
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
catch {
|
|
2634
|
+
// Transparency is best-effort — never block message delivery
|
|
2635
|
+
}
|
|
1942
2636
|
// Save assistant message (skip if empty — happens when response is tool-only)
|
|
1943
|
-
if (
|
|
1944
|
-
await this.sessions.addMessage(session.id, 'assistant',
|
|
2637
|
+
if (finalResponse) {
|
|
2638
|
+
await this.sessions.addMessage(session.id, 'assistant', finalResponse, {
|
|
1945
2639
|
input: usage.inputTokens,
|
|
1946
2640
|
output: usage.outputTokens,
|
|
2641
|
+
}, {
|
|
2642
|
+
...(architectResult.architectMeta ? { architectDomain: architectResult.architectMeta.detectedContext.domain } : {}),
|
|
2643
|
+
...(transparencyMeta ? { transparency: transparencyMeta } : {}),
|
|
1947
2644
|
});
|
|
1948
2645
|
}
|
|
1949
2646
|
// Record usage for cost tracking
|
|
@@ -1951,14 +2648,14 @@ export class Auxiora {
|
|
|
1951
2648
|
this.modelRouter.recordUsage(routingResult.selection.provider, routingResult.selection.model, usage.inputTokens, usage.outputTokens);
|
|
1952
2649
|
}
|
|
1953
2650
|
// Extract memories and learn from conversation (if auto-extract enabled)
|
|
1954
|
-
if (this.config.memory?.autoExtract !== false && this.memoryStore &&
|
|
1955
|
-
void this.extractAndLearn(
|
|
2651
|
+
if (this.config.memory?.autoExtract !== false && this.memoryStore && finalResponse && processedContent.length > 20) {
|
|
2652
|
+
void this.extractAndLearn(processedContent, finalResponse, session.id);
|
|
1956
2653
|
}
|
|
1957
2654
|
// Auto-title webchat chats after first exchange
|
|
1958
|
-
if (
|
|
2655
|
+
if (finalResponse &&
|
|
1959
2656
|
session.metadata.channelType === 'webchat' &&
|
|
1960
2657
|
session.messages.length <= 3) {
|
|
1961
|
-
void this.generateChatTitle(session.id,
|
|
2658
|
+
void this.generateChatTitle(session.id, processedContent, finalResponse, client);
|
|
1962
2659
|
}
|
|
1963
2660
|
// Send done signal
|
|
1964
2661
|
this.sendToClient(client, {
|
|
@@ -1977,6 +2674,7 @@ export class Auxiora {
|
|
|
1977
2674
|
override: true,
|
|
1978
2675
|
} : undefined,
|
|
1979
2676
|
architect: architectResult.architectMeta,
|
|
2677
|
+
transparency: transparencyMeta,
|
|
1980
2678
|
},
|
|
1981
2679
|
});
|
|
1982
2680
|
// Background self-awareness analysis
|
|
@@ -1985,13 +2683,33 @@ export class Auxiora {
|
|
|
1985
2683
|
userId: client.senderId ?? 'anonymous',
|
|
1986
2684
|
sessionId: session.id,
|
|
1987
2685
|
chatId: chatId ?? session.id,
|
|
1988
|
-
currentMessage:
|
|
2686
|
+
currentMessage: processedContent,
|
|
1989
2687
|
recentMessages: contextMessages,
|
|
1990
|
-
response:
|
|
2688
|
+
response: finalResponse,
|
|
1991
2689
|
responseTime: Date.now() - (session.metadata.lastActiveAt ?? Date.now()),
|
|
1992
2690
|
tokensUsed: { input: usage?.inputTokens ?? 0, output: usage?.outputTokens ?? 0 },
|
|
2691
|
+
streamChunks: streamChunkCount,
|
|
1993
2692
|
}).catch(() => { });
|
|
1994
2693
|
}
|
|
2694
|
+
// Record conversation in consciousness journal
|
|
2695
|
+
if (this.consciousness) {
|
|
2696
|
+
const journalBase = {
|
|
2697
|
+
sessionId: session.id,
|
|
2698
|
+
type: 'message',
|
|
2699
|
+
context: {
|
|
2700
|
+
domains: architectResult.architectMeta
|
|
2701
|
+
? [architectResult.architectMeta.detectedContext.domain]
|
|
2702
|
+
: ['general'],
|
|
2703
|
+
},
|
|
2704
|
+
selfState: {
|
|
2705
|
+
health: (this.healthMonitor?.getHealthState().overall === 'unhealthy' ? 'degraded' : this.healthMonitor?.getHealthState().overall ?? 'healthy'),
|
|
2706
|
+
activeProviders: [this.config.provider.primary],
|
|
2707
|
+
uptime: Math.round(process.uptime()),
|
|
2708
|
+
},
|
|
2709
|
+
};
|
|
2710
|
+
this.consciousness.journal.record({ ...journalBase, message: { role: 'user', content: processedContent } }).catch(() => { });
|
|
2711
|
+
this.consciousness.journal.record({ ...journalBase, message: { role: 'assistant', content: finalResponse } }).catch(() => { });
|
|
2712
|
+
}
|
|
1995
2713
|
audit('message.sent', {
|
|
1996
2714
|
sessionId: session.id,
|
|
1997
2715
|
inputTokens: usage.inputTokens,
|
|
@@ -2268,6 +2986,14 @@ export class Auxiora {
|
|
|
2268
2986
|
for (const toolUse of toolUses) {
|
|
2269
2987
|
// Map Claude Code emulation tool names to our actual tools
|
|
2270
2988
|
const mapped = mapCCToolCall(toolUse.name, toolUse.input);
|
|
2989
|
+
// Skip CC-only tools that have no Auxiora equivalent
|
|
2990
|
+
if (mapped.skip) {
|
|
2991
|
+
onChunk('tool_result', { tool: toolUse.name, success: false, error: mapped.skip });
|
|
2992
|
+
toolResultParts.push(`[${toolUse.name}]: Error: ${mapped.skip}`);
|
|
2993
|
+
recordToolCall(loopState, toolUse.id, mapped.name, mapped.input);
|
|
2994
|
+
recordToolOutcome(loopState, toolUse.id, mapped.skip);
|
|
2995
|
+
continue;
|
|
2996
|
+
}
|
|
2271
2997
|
recordToolCall(loopState, toolUse.id, mapped.name, mapped.input);
|
|
2272
2998
|
try {
|
|
2273
2999
|
const result = await toolExecutor.execute(mapped.name, mapped.input, context);
|
|
@@ -2511,7 +3237,11 @@ export class Auxiora {
|
|
|
2511
3237
|
persistToWebchat(content) {
|
|
2512
3238
|
this.sessions.getOrCreate('webchat', { channelType: 'webchat' })
|
|
2513
3239
|
.then(session => this.sessions.addMessage(session.id, 'assistant', content))
|
|
2514
|
-
.catch(() => {
|
|
3240
|
+
.catch((err) => {
|
|
3241
|
+
this.logger.warn('Failed to persist webchat message', {
|
|
3242
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
3243
|
+
});
|
|
3244
|
+
});
|
|
2515
3245
|
}
|
|
2516
3246
|
/** Deliver a proactive message to all connected channel adapters using tracked channel IDs.
|
|
2517
3247
|
* Also persists to the webchat session so messages appear in chat history.
|
|
@@ -2563,149 +3293,253 @@ export class Auxiora {
|
|
|
2563
3293
|
if (inbound.attachments && inbound.attachments.length > 0 && this.mediaProcessor) {
|
|
2564
3294
|
messageContent = await this.mediaProcessor.process(inbound.attachments, inbound.content);
|
|
2565
3295
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
if (
|
|
3296
|
+
// ── Guardrail input scan ──────────────────────────────────────
|
|
3297
|
+
const inputScan = this.checkInputGuardrails(messageContent);
|
|
3298
|
+
if (inputScan && inputScan.action === 'block') {
|
|
3299
|
+
audit('guardrail.triggered', {
|
|
3300
|
+
action: 'block',
|
|
3301
|
+
direction: 'input',
|
|
3302
|
+
threatCount: inputScan.threats.length,
|
|
3303
|
+
channelType: inbound.channelType,
|
|
3304
|
+
sessionId: session.id,
|
|
3305
|
+
});
|
|
2569
3306
|
if (this.channels) {
|
|
2570
3307
|
await this.channels.send(inbound.channelType, inbound.channelId, {
|
|
2571
|
-
content:
|
|
3308
|
+
content: this.GUARDRAIL_BLOCK_MESSAGE,
|
|
2572
3309
|
replyToId: inbound.id,
|
|
2573
3310
|
});
|
|
2574
3311
|
}
|
|
2575
3312
|
return;
|
|
2576
3313
|
}
|
|
2577
|
-
//
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
3314
|
+
// Apply redaction if guardrails flagged PII
|
|
3315
|
+
if (inputScan?.action === 'redact' && inputScan.redactedContent) {
|
|
3316
|
+
messageContent = inputScan.redactedContent;
|
|
3317
|
+
audit('guardrail.triggered', {
|
|
3318
|
+
action: 'redact',
|
|
3319
|
+
direction: 'input',
|
|
3320
|
+
threatCount: inputScan.threats.length,
|
|
3321
|
+
channelType: inbound.channelType,
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
else if (inputScan?.action === 'warn') {
|
|
3325
|
+
audit('guardrail.triggered', {
|
|
3326
|
+
action: 'warn',
|
|
3327
|
+
direction: 'input',
|
|
3328
|
+
threatCount: inputScan.threats.length,
|
|
3329
|
+
channelType: inbound.channelType,
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
await this.sessions.addMessage(session.id, 'user', messageContent);
|
|
3333
|
+
// Check if providers are available
|
|
3334
|
+
if (!this.providers) {
|
|
3335
|
+
if (this.channels) {
|
|
3336
|
+
await this.channels.send(inbound.channelType, inbound.channelId, {
|
|
3337
|
+
content: 'I need API keys to respond. Please configure them in the vault.',
|
|
3338
|
+
replyToId: inbound.id,
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
// Get context messages — channel sessions use a capped token budget and turn limit
|
|
3344
|
+
// to prevent excessively long API calls from models with huge context windows.
|
|
3345
|
+
const contextMessages = this.sessions.getContextMessages(session.id, this.getProviderMaxTokens(this.providers.getPrimaryProvider()), 4096, { isChannel: true });
|
|
3346
|
+
const chatMessages = sanitizeTranscript(contextMessages).map((m) => ({
|
|
3347
|
+
role: m.role,
|
|
3348
|
+
content: m.content,
|
|
3349
|
+
}));
|
|
3350
|
+
// Show typing indicator while generating response
|
|
3351
|
+
const stopTyping = this.channels
|
|
3352
|
+
? await this.channels.startTyping(inbound.channelType, inbound.channelId)
|
|
3353
|
+
: () => { };
|
|
2587
3354
|
const channelAgentId = `channel:${inbound.channelType}:${inbound.channelId}:${Date.now()}`;
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
3355
|
+
// 4-minute timeout for the entire LLM response cycle.
|
|
3356
|
+
// Increased from 2min to accommodate auto-continuations (max_tokens → "Continue")
|
|
3357
|
+
// and tool round-trips. If the provider stream hangs (network issue, overloaded API),
|
|
3358
|
+
// this ensures the user gets an error message instead of infinite "typing…".
|
|
3359
|
+
const CHANNEL_RESPONSE_TIMEOUT_MS = 240_000;
|
|
3360
|
+
let draftLoop = null;
|
|
3361
|
+
let draftMessageId = null;
|
|
3362
|
+
try { // outer try — finally block guarantees stopTyping() runs
|
|
3363
|
+
try {
|
|
3364
|
+
// Get tool definitions from registry
|
|
3365
|
+
const tools = toolRegistry.toProviderFormat();
|
|
3366
|
+
// Build enriched prompt through pipeline
|
|
3367
|
+
let enrichedPrompt = this.systemPrompt;
|
|
3368
|
+
const channelChatId = `${inbound.channelType}:${inbound.channelId}`;
|
|
3369
|
+
let channelArchitectResult = { prompt: this.systemPrompt };
|
|
3370
|
+
// Reset Architect conversation state for new channel chats
|
|
3371
|
+
const useChannelArchitect = this.config.agent.personality === 'the-architect';
|
|
3372
|
+
if (useChannelArchitect && this.architect && !this.architectResetChats.has(channelChatId)) {
|
|
3373
|
+
this.architectResetChats.add(channelChatId);
|
|
3374
|
+
this.architect.resetConversation();
|
|
3375
|
+
audit('personality.reset', { sessionId: session.id, chatId: channelChatId });
|
|
3376
|
+
}
|
|
3377
|
+
if (this.enrichmentPipeline) {
|
|
3378
|
+
const enrichCtx = {
|
|
3379
|
+
basePrompt: this.systemPrompt,
|
|
3380
|
+
userMessage: messageContent,
|
|
3381
|
+
history: contextMessages,
|
|
3382
|
+
channelType: inbound.channelType,
|
|
3383
|
+
chatId: channelChatId,
|
|
3384
|
+
sessionId: session.id,
|
|
3385
|
+
userId: inbound.senderId ?? 'anonymous',
|
|
3386
|
+
toolsUsed: this.lastToolsUsed.get(session.id) ?? [],
|
|
3387
|
+
config: this.config,
|
|
3388
|
+
};
|
|
3389
|
+
const result = await this.enrichmentPipeline.run(enrichCtx);
|
|
3390
|
+
enrichedPrompt = result.prompt;
|
|
3391
|
+
channelArchitectResult = { prompt: enrichedPrompt, architectMeta: result.metadata.architect };
|
|
3392
|
+
}
|
|
3393
|
+
// Use executeWithTools for channels — collect final text for channel reply
|
|
3394
|
+
const provider = this.providers.getPrimaryProvider();
|
|
3395
|
+
// Inject model identity so the AI knows what it's running on
|
|
3396
|
+
enrichedPrompt += this.buildModelIdentityFragment(provider);
|
|
3397
|
+
this.agentStart(channelAgentId, 'channel', `Processing message on ${inbound.channelType}`, inbound.channelType);
|
|
3398
|
+
// Draft streaming: edit message in place if adapter supports it
|
|
3399
|
+
const adapter = this.channels?.getAdapter(inbound.channelType);
|
|
3400
|
+
const supportsDraft = !!adapter?.editMessage;
|
|
3401
|
+
let accumulatedText = '';
|
|
3402
|
+
if (supportsDraft && this.channels) {
|
|
3403
|
+
const channels = this.channels;
|
|
3404
|
+
draftLoop = new DraftStreamLoop(async (text) => {
|
|
3405
|
+
try {
|
|
3406
|
+
if (!draftMessageId) {
|
|
3407
|
+
const result = await channels.send(inbound.channelType, inbound.channelId, {
|
|
3408
|
+
content: text,
|
|
3409
|
+
replyToId: inbound.id,
|
|
3410
|
+
});
|
|
3411
|
+
if (result.success && result.messageId) {
|
|
3412
|
+
draftMessageId = result.messageId;
|
|
3413
|
+
}
|
|
3414
|
+
return result.success;
|
|
3415
|
+
}
|
|
3416
|
+
else {
|
|
3417
|
+
const result = await channels.editMessage(inbound.channelType, inbound.channelId, draftMessageId, { content: text });
|
|
3418
|
+
return result.success;
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
catch {
|
|
3422
|
+
return false;
|
|
3423
|
+
}
|
|
3424
|
+
}, 1000);
|
|
3425
|
+
}
|
|
3426
|
+
const fallbackCandidates = this.providers.resolveFallbackCandidates();
|
|
3427
|
+
const channelToolsUsed = [];
|
|
3428
|
+
const { response: channelResponse, usage: channelUsage } = await Promise.race([
|
|
3429
|
+
this.executeWithTools(session.id, chatMessages, enrichedPrompt, provider, (type, data) => {
|
|
3430
|
+
if (type === 'text' && data && draftLoop) {
|
|
3431
|
+
accumulatedText += data;
|
|
3432
|
+
draftLoop.update(accumulatedText);
|
|
3433
|
+
}
|
|
3434
|
+
else if (type === 'tool_use') {
|
|
3435
|
+
channelToolsUsed.push({ name: data?.name ?? 'unknown', success: true });
|
|
3436
|
+
}
|
|
3437
|
+
else if (type === 'tool_result') {
|
|
3438
|
+
if (channelToolsUsed.length > 0 && data?.error) {
|
|
3439
|
+
channelToolsUsed[channelToolsUsed.length - 1].success = false;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
}, { tools, fallbackCandidates }),
|
|
3443
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Response timed out — the AI provider did not respond within 4 minutes. Please try again.')), CHANNEL_RESPONSE_TIMEOUT_MS)),
|
|
3444
|
+
]);
|
|
3445
|
+
// Feed tool usage to awareness collector
|
|
3446
|
+
if (this.architectAwarenessCollector && channelToolsUsed.length > 0) {
|
|
3447
|
+
this.architectAwarenessCollector.updateToolContext(channelToolsUsed);
|
|
3448
|
+
}
|
|
3449
|
+
this.lastToolsUsed.set(session.id, channelToolsUsed);
|
|
3450
|
+
// Flush final draft text
|
|
3451
|
+
if (draftLoop) {
|
|
3452
|
+
if (channelResponse && channelResponse !== accumulatedText) {
|
|
3453
|
+
draftLoop.update(channelResponse);
|
|
2606
3454
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
3455
|
+
await draftLoop.flush();
|
|
3456
|
+
draftLoop.stop();
|
|
3457
|
+
}
|
|
3458
|
+
// ── Guardrail output scan ─────────────────────────────────────
|
|
3459
|
+
const channelOutputScan = this.checkOutputGuardrails(channelResponse);
|
|
3460
|
+
const finalChannelResponse = channelOutputScan.response;
|
|
3461
|
+
if (channelOutputScan.wasModified) {
|
|
3462
|
+
audit('guardrail.triggered', {
|
|
3463
|
+
action: channelOutputScan.action,
|
|
3464
|
+
direction: 'output',
|
|
3465
|
+
channelType: inbound.channelType,
|
|
3466
|
+
sessionId: session.id,
|
|
3467
|
+
});
|
|
3468
|
+
// If draft streaming already sent partial text, do a final edit with clean version
|
|
3469
|
+
if (draftMessageId && adapter?.editMessage) {
|
|
3470
|
+
await adapter.editMessage(inbound.channelId, draftMessageId, { content: finalChannelResponse });
|
|
2611
3471
|
}
|
|
2612
|
-
|
|
2613
|
-
|
|
3472
|
+
}
|
|
3473
|
+
// Save assistant message
|
|
3474
|
+
await this.sessions.addMessage(session.id, 'assistant', finalChannelResponse, {
|
|
3475
|
+
input: channelUsage.inputTokens,
|
|
3476
|
+
output: channelUsage.outputTokens,
|
|
3477
|
+
}, channelArchitectResult.architectMeta ? { architectDomain: channelArchitectResult.architectMeta.detectedContext.domain } : undefined);
|
|
3478
|
+
// Extract memories and learn from conversation (if auto-extract enabled)
|
|
3479
|
+
if (this.config.memory?.autoExtract !== false && this.memoryStore && finalChannelResponse && messageContent.length > 20) {
|
|
3480
|
+
void this.extractAndLearn(messageContent, finalChannelResponse, session.id);
|
|
3481
|
+
}
|
|
3482
|
+
// Send final response. The draft stream loop edits a single message,
|
|
3483
|
+
// but Discord silently truncates edits at 2000 chars. For long responses,
|
|
3484
|
+
// replace the draft with a chunked send so nothing is lost.
|
|
3485
|
+
const DRAFT_SAFE_LENGTH = 1900; // leave margin below Discord's 2000 char limit
|
|
3486
|
+
if (draftMessageId && this.channels && finalChannelResponse.length > DRAFT_SAFE_LENGTH) {
|
|
3487
|
+
// Draft only showed partial content — replace it with a pointer and send full chunked response
|
|
3488
|
+
if (adapter?.editMessage) {
|
|
3489
|
+
await adapter.editMessage(inbound.channelId, draftMessageId, {
|
|
3490
|
+
content: '*\u2026 (full response below)*',
|
|
3491
|
+
});
|
|
2614
3492
|
}
|
|
3493
|
+
await this.channels.send(inbound.channelType, inbound.channelId, {
|
|
3494
|
+
content: finalChannelResponse,
|
|
3495
|
+
});
|
|
2615
3496
|
}
|
|
2616
|
-
else {
|
|
2617
|
-
|
|
3497
|
+
else if (!draftMessageId && this.channels) {
|
|
3498
|
+
await this.channels.send(inbound.channelType, inbound.channelId, {
|
|
3499
|
+
content: finalChannelResponse,
|
|
3500
|
+
replyToId: inbound.id,
|
|
3501
|
+
});
|
|
2618
3502
|
}
|
|
3503
|
+
audit('message.sent', {
|
|
3504
|
+
channelType: inbound.channelType,
|
|
3505
|
+
sessionId: session.id,
|
|
3506
|
+
inputTokens: channelUsage.inputTokens,
|
|
3507
|
+
outputTokens: channelUsage.outputTokens,
|
|
3508
|
+
});
|
|
3509
|
+
this.agentEnd(channelAgentId, true);
|
|
2619
3510
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
try {
|
|
2638
|
-
if (!draftMessageId) {
|
|
2639
|
-
const result = await channels.send(inbound.channelType, inbound.channelId, {
|
|
2640
|
-
content: text,
|
|
3511
|
+
catch (error) {
|
|
3512
|
+
if (draftLoop)
|
|
3513
|
+
draftLoop.stop();
|
|
3514
|
+
this.agentEnd(channelAgentId, false);
|
|
3515
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
3516
|
+
audit('channel.error', { sessionId: session.id, error: errorMessage });
|
|
3517
|
+
if (this.channels) {
|
|
3518
|
+
const errorContent = `Error: ${errorMessage}`;
|
|
3519
|
+
// If a draft message exists, edit it with the error instead of sending a new one
|
|
3520
|
+
if (draftMessageId) {
|
|
3521
|
+
try {
|
|
3522
|
+
await this.channels.editMessage(inbound.channelType, inbound.channelId, draftMessageId, { content: errorContent });
|
|
3523
|
+
}
|
|
3524
|
+
catch {
|
|
3525
|
+
// Edit failed — fall back to new message
|
|
3526
|
+
await this.channels.send(inbound.channelType, inbound.channelId, {
|
|
3527
|
+
content: errorContent,
|
|
2641
3528
|
replyToId: inbound.id,
|
|
2642
3529
|
});
|
|
2643
|
-
if (result.success && result.messageId) {
|
|
2644
|
-
draftMessageId = result.messageId;
|
|
2645
|
-
}
|
|
2646
|
-
return result.success;
|
|
2647
|
-
}
|
|
2648
|
-
else {
|
|
2649
|
-
const result = await channels.editMessage(inbound.channelType, inbound.channelId, draftMessageId, { content: text });
|
|
2650
|
-
return result.success;
|
|
2651
3530
|
}
|
|
2652
3531
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
3532
|
+
else {
|
|
3533
|
+
await this.channels.send(inbound.channelType, inbound.channelId, {
|
|
3534
|
+
content: errorContent,
|
|
3535
|
+
replyToId: inbound.id,
|
|
3536
|
+
});
|
|
2655
3537
|
}
|
|
2656
|
-
}, 1000);
|
|
2657
|
-
}
|
|
2658
|
-
const fallbackCandidates = this.providers.resolveFallbackCandidates();
|
|
2659
|
-
const { response: channelResponse, usage: channelUsage } = await this.executeWithTools(session.id, chatMessages, enrichedPrompt, provider, (type, data) => {
|
|
2660
|
-
if (type === 'text' && data && draftLoop) {
|
|
2661
|
-
accumulatedText += data;
|
|
2662
|
-
draftLoop.update(accumulatedText);
|
|
2663
|
-
}
|
|
2664
|
-
}, { tools, fallbackCandidates });
|
|
2665
|
-
// Flush final draft text
|
|
2666
|
-
if (draftLoop) {
|
|
2667
|
-
if (channelResponse && channelResponse !== accumulatedText) {
|
|
2668
|
-
draftLoop.update(channelResponse);
|
|
2669
3538
|
}
|
|
2670
|
-
await draftLoop.flush();
|
|
2671
|
-
draftLoop.stop();
|
|
2672
3539
|
}
|
|
2673
|
-
stopTyping();
|
|
2674
|
-
// Save assistant message
|
|
2675
|
-
await this.sessions.addMessage(session.id, 'assistant', channelResponse, {
|
|
2676
|
-
input: channelUsage.inputTokens,
|
|
2677
|
-
output: channelUsage.outputTokens,
|
|
2678
|
-
});
|
|
2679
|
-
// Extract memories and learn from conversation (if auto-extract enabled)
|
|
2680
|
-
if (this.config.memory?.autoExtract !== false && this.memoryStore && channelResponse && inbound.content.length > 20) {
|
|
2681
|
-
void this.extractAndLearn(inbound.content, channelResponse, session.id);
|
|
2682
|
-
}
|
|
2683
|
-
// Send response (skip if draft streaming already delivered it)
|
|
2684
|
-
if (!draftMessageId && this.channels) {
|
|
2685
|
-
await this.channels.send(inbound.channelType, inbound.channelId, {
|
|
2686
|
-
content: channelResponse,
|
|
2687
|
-
replyToId: inbound.id,
|
|
2688
|
-
});
|
|
2689
|
-
}
|
|
2690
|
-
audit('message.sent', {
|
|
2691
|
-
channelType: inbound.channelType,
|
|
2692
|
-
sessionId: session.id,
|
|
2693
|
-
inputTokens: channelUsage.inputTokens,
|
|
2694
|
-
outputTokens: channelUsage.outputTokens,
|
|
2695
|
-
});
|
|
2696
|
-
this.agentEnd(channelAgentId, true);
|
|
2697
3540
|
}
|
|
2698
|
-
|
|
3541
|
+
finally {
|
|
2699
3542
|
stopTyping();
|
|
2700
|
-
this.agentEnd(channelAgentId, false);
|
|
2701
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
2702
|
-
audit('channel.error', { sessionId: session.id, error: errorMessage });
|
|
2703
|
-
if (this.channels) {
|
|
2704
|
-
await this.channels.send(inbound.channelType, inbound.channelId, {
|
|
2705
|
-
content: `Error: ${errorMessage}`,
|
|
2706
|
-
replyToId: inbound.id,
|
|
2707
|
-
});
|
|
2708
|
-
}
|
|
2709
3543
|
}
|
|
2710
3544
|
}); // end runWithRequestId
|
|
2711
3545
|
}
|
|
@@ -2912,10 +3746,52 @@ export class Auxiora {
|
|
|
2912
3746
|
}
|
|
2913
3747
|
// Load persisted channel targets for proactive delivery (behaviors, ambient)
|
|
2914
3748
|
await this.loadChannelTargets();
|
|
3749
|
+
// Research job expiry (every 60s, prune jobs older than 1 hour)
|
|
3750
|
+
this.researchJobExpiry = setInterval(() => {
|
|
3751
|
+
const ONE_HOUR = 3_600_000;
|
|
3752
|
+
const now = Date.now();
|
|
3753
|
+
for (const [id, job] of this.researchJobs) {
|
|
3754
|
+
if (now - job.createdAt > ONE_HOUR)
|
|
3755
|
+
this.researchJobs.delete(id);
|
|
3756
|
+
}
|
|
3757
|
+
}, 60_000);
|
|
2915
3758
|
this.running = true;
|
|
2916
3759
|
console.log(`\n${this.getAgentName()} is ready!`);
|
|
2917
3760
|
console.log(`Open http://${this.config.gateway.host}:${this.config.gateway.port} in your browser\n`);
|
|
2918
3761
|
}
|
|
3762
|
+
async processEventTriggers(events) {
|
|
3763
|
+
if (!this.behaviors || events.length === 0)
|
|
3764
|
+
return;
|
|
3765
|
+
const allBehaviors = await this.behaviors.list();
|
|
3766
|
+
const eventBehaviors = allBehaviors.filter((b) => b.type === 'event' && b.status === 'active' && b.eventTrigger);
|
|
3767
|
+
for (const event of events) {
|
|
3768
|
+
// Feed to ambient pattern engine
|
|
3769
|
+
this.ambientEngine?.observe({
|
|
3770
|
+
type: `${event.connectorId}:${event.triggerId}`,
|
|
3771
|
+
timestamp: event.timestamp,
|
|
3772
|
+
data: event.data,
|
|
3773
|
+
});
|
|
3774
|
+
// Match against event behaviors
|
|
3775
|
+
for (const behavior of eventBehaviors) {
|
|
3776
|
+
const trigger = behavior.eventTrigger;
|
|
3777
|
+
if (trigger.source !== event.connectorId || trigger.event !== event.triggerId)
|
|
3778
|
+
continue;
|
|
3779
|
+
if (evaluateConditions(event.data, trigger.conditions, trigger.combinator)) {
|
|
3780
|
+
try {
|
|
3781
|
+
await this.behaviors.executeNow(behavior.id);
|
|
3782
|
+
await audit('behavior.event_triggered', {
|
|
3783
|
+
behaviorId: behavior.id,
|
|
3784
|
+
source: event.connectorId,
|
|
3785
|
+
event: event.triggerId,
|
|
3786
|
+
});
|
|
3787
|
+
}
|
|
3788
|
+
catch {
|
|
3789
|
+
// Execution failures tracked by BehaviorManager
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
2919
3795
|
async stop() {
|
|
2920
3796
|
if (!this.running)
|
|
2921
3797
|
return;
|
|
@@ -2938,6 +3814,9 @@ export class Auxiora {
|
|
|
2938
3814
|
if (this.ambientScheduler) {
|
|
2939
3815
|
this.ambientScheduler.stop();
|
|
2940
3816
|
}
|
|
3817
|
+
if (this.ambientDetectTimer) {
|
|
3818
|
+
clearInterval(this.ambientDetectTimer);
|
|
3819
|
+
}
|
|
2941
3820
|
if (this.autonomousExecutor) {
|
|
2942
3821
|
this.autonomousExecutor.stop();
|
|
2943
3822
|
}
|
|
@@ -2945,6 +3824,17 @@ export class Auxiora {
|
|
|
2945
3824
|
clearInterval(this.memoryCleanupInterval);
|
|
2946
3825
|
this.memoryCleanupInterval = undefined;
|
|
2947
3826
|
}
|
|
3827
|
+
if (this.researchJobExpiry) {
|
|
3828
|
+
clearInterval(this.researchJobExpiry);
|
|
3829
|
+
this.researchJobExpiry = undefined;
|
|
3830
|
+
}
|
|
3831
|
+
if (this.mcpClientManager) {
|
|
3832
|
+
await this.mcpClientManager.disconnectAll();
|
|
3833
|
+
}
|
|
3834
|
+
if (this.jobQueue) {
|
|
3835
|
+
await this.jobQueue.stop(30000);
|
|
3836
|
+
}
|
|
3837
|
+
this.consciousness?.shutdown();
|
|
2948
3838
|
this.sessions.destroy();
|
|
2949
3839
|
this.vault.lock();
|
|
2950
3840
|
this.running = false;
|
|
@@ -2955,6 +3845,2119 @@ export class Auxiora {
|
|
|
2955
3845
|
getAgentName() {
|
|
2956
3846
|
return this.config.agent?.name ?? 'Auxiora';
|
|
2957
3847
|
}
|
|
3848
|
+
createPersonalityRouter() {
|
|
3849
|
+
const router = Router();
|
|
3850
|
+
const guard = (_req, res) => {
|
|
3851
|
+
if (!this.architect) {
|
|
3852
|
+
res.status(503).json({ error: 'Architect not available' });
|
|
3853
|
+
return false;
|
|
3854
|
+
}
|
|
3855
|
+
return true;
|
|
3856
|
+
};
|
|
3857
|
+
// --- Decisions (Gap 4) ---
|
|
3858
|
+
router.post('/decisions', async (req, res) => {
|
|
3859
|
+
if (!guard(req, res))
|
|
3860
|
+
return;
|
|
3861
|
+
const { domain, summary, context, followUpDate } = req.body ?? {};
|
|
3862
|
+
if (!domain || !summary || !context) {
|
|
3863
|
+
res.status(400).json({ error: 'Missing required fields: domain, summary, context' });
|
|
3864
|
+
return;
|
|
3865
|
+
}
|
|
3866
|
+
try {
|
|
3867
|
+
const decision = await this.architect.recordDecision({ domain, summary, context, followUpDate, status: 'active' });
|
|
3868
|
+
audit('personality.decision.created', { decisionId: decision.id, domain });
|
|
3869
|
+
res.status(201).json(decision);
|
|
3870
|
+
}
|
|
3871
|
+
catch (err) {
|
|
3872
|
+
res.status(500).json({ error: err.message ?? 'Failed to record decision' });
|
|
3873
|
+
}
|
|
3874
|
+
});
|
|
3875
|
+
router.patch('/decisions/:id', async (req, res) => {
|
|
3876
|
+
if (!guard(req, res))
|
|
3877
|
+
return;
|
|
3878
|
+
const { status, outcome, followUpDate } = req.body ?? {};
|
|
3879
|
+
try {
|
|
3880
|
+
await this.architect.updateDecision(req.params.id, { status, outcome, followUpDate });
|
|
3881
|
+
audit('personality.decision.updated', { decisionId: req.params.id });
|
|
3882
|
+
res.json({ ok: true });
|
|
3883
|
+
}
|
|
3884
|
+
catch (err) {
|
|
3885
|
+
res.status(500).json({ error: err.message ?? 'Failed to update decision' });
|
|
3886
|
+
}
|
|
3887
|
+
});
|
|
3888
|
+
router.get('/decisions', async (req, res) => {
|
|
3889
|
+
if (!guard(req, res))
|
|
3890
|
+
return;
|
|
3891
|
+
try {
|
|
3892
|
+
const { domain, status, since, search, limit } = req.query;
|
|
3893
|
+
const query = {};
|
|
3894
|
+
if (domain)
|
|
3895
|
+
query.domain = domain;
|
|
3896
|
+
if (status)
|
|
3897
|
+
query.status = status;
|
|
3898
|
+
if (since)
|
|
3899
|
+
query.since = since;
|
|
3900
|
+
if (search)
|
|
3901
|
+
query.search = search;
|
|
3902
|
+
if (limit)
|
|
3903
|
+
query.limit = Number(limit);
|
|
3904
|
+
const decisions = await this.architect.queryDecisions(query);
|
|
3905
|
+
res.json({ decisions });
|
|
3906
|
+
}
|
|
3907
|
+
catch (err) {
|
|
3908
|
+
res.status(500).json({ error: err.message ?? 'Failed to query decisions' });
|
|
3909
|
+
}
|
|
3910
|
+
});
|
|
3911
|
+
router.get('/decisions/due', async (req, res) => {
|
|
3912
|
+
if (!guard(req, res))
|
|
3913
|
+
return;
|
|
3914
|
+
try {
|
|
3915
|
+
const due = await this.architect.getDueFollowUps();
|
|
3916
|
+
res.json({ decisions: due });
|
|
3917
|
+
}
|
|
3918
|
+
catch (err) {
|
|
3919
|
+
res.status(500).json({ error: err.message ?? 'Failed to get due follow-ups' });
|
|
3920
|
+
}
|
|
3921
|
+
});
|
|
3922
|
+
// --- Traits (Gap 5) ---
|
|
3923
|
+
router.get('/traits', (req, res) => {
|
|
3924
|
+
if (!guard(req, res))
|
|
3925
|
+
return;
|
|
3926
|
+
try {
|
|
3927
|
+
const mix = this.architect.getTraitMix({
|
|
3928
|
+
domain: 'general',
|
|
3929
|
+
emotionalRegister: 'neutral',
|
|
3930
|
+
stakes: 'moderate',
|
|
3931
|
+
complexity: 'moderate',
|
|
3932
|
+
mode: 'solo_work',
|
|
3933
|
+
});
|
|
3934
|
+
const overrides = this.architect.getActiveOverrides();
|
|
3935
|
+
res.json({ mix, overrides });
|
|
3936
|
+
}
|
|
3937
|
+
catch (err) {
|
|
3938
|
+
res.status(500).json({ error: err.message ?? 'Failed to get traits' });
|
|
3939
|
+
}
|
|
3940
|
+
});
|
|
3941
|
+
router.put('/traits/:trait', async (req, res) => {
|
|
3942
|
+
if (!guard(req, res))
|
|
3943
|
+
return;
|
|
3944
|
+
const { offset, source, reason } = req.body ?? {};
|
|
3945
|
+
if (typeof offset !== 'number') {
|
|
3946
|
+
res.status(400).json({ error: 'Missing or invalid field: offset must be a number' });
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
try {
|
|
3950
|
+
await this.architect.setTraitOverride(req.params.trait, offset);
|
|
3951
|
+
audit('personality.trait.override', { trait: req.params.trait, offset, source, reason });
|
|
3952
|
+
res.json({ ok: true });
|
|
3953
|
+
}
|
|
3954
|
+
catch (err) {
|
|
3955
|
+
res.status(500).json({ error: err.message ?? 'Failed to set trait override' });
|
|
3956
|
+
}
|
|
3957
|
+
});
|
|
3958
|
+
router.delete('/traits/:trait', async (req, res) => {
|
|
3959
|
+
if (!guard(req, res))
|
|
3960
|
+
return;
|
|
3961
|
+
try {
|
|
3962
|
+
await this.architect.removeTraitOverride(req.params.trait);
|
|
3963
|
+
audit('personality.trait.override', { trait: req.params.trait, action: 'removed' });
|
|
3964
|
+
res.json({ ok: true });
|
|
3965
|
+
}
|
|
3966
|
+
catch (err) {
|
|
3967
|
+
res.status(500).json({ error: err.message ?? 'Failed to remove trait override' });
|
|
3968
|
+
}
|
|
3969
|
+
});
|
|
3970
|
+
// --- Presets (Gap 5) ---
|
|
3971
|
+
router.get('/presets', (req, res) => {
|
|
3972
|
+
if (!guard(req, res))
|
|
3973
|
+
return;
|
|
3974
|
+
try {
|
|
3975
|
+
const presets = this.architect.listPresets();
|
|
3976
|
+
res.json({ presets });
|
|
3977
|
+
}
|
|
3978
|
+
catch (err) {
|
|
3979
|
+
res.status(500).json({ error: err.message ?? 'Failed to list presets' });
|
|
3980
|
+
}
|
|
3981
|
+
});
|
|
3982
|
+
router.post('/presets/:name/apply', async (req, res) => {
|
|
3983
|
+
if (!guard(req, res))
|
|
3984
|
+
return;
|
|
3985
|
+
try {
|
|
3986
|
+
await this.architect.loadPreset(req.params.name);
|
|
3987
|
+
audit('personality.preset.applied', { preset: req.params.name });
|
|
3988
|
+
res.json({ ok: true });
|
|
3989
|
+
}
|
|
3990
|
+
catch (err) {
|
|
3991
|
+
res.status(500).json({ error: err.message ?? 'Failed to apply preset' });
|
|
3992
|
+
}
|
|
3993
|
+
});
|
|
3994
|
+
// --- Preferences (Gap 12) ---
|
|
3995
|
+
router.get('/preferences', async (req, res) => {
|
|
3996
|
+
if (!guard(req, res))
|
|
3997
|
+
return;
|
|
3998
|
+
try {
|
|
3999
|
+
const prefs = await this.architect.getPreferences();
|
|
4000
|
+
res.json(prefs);
|
|
4001
|
+
}
|
|
4002
|
+
catch (err) {
|
|
4003
|
+
res.status(500).json({ error: err.message ?? 'Failed to get preferences' });
|
|
4004
|
+
}
|
|
4005
|
+
});
|
|
4006
|
+
router.put('/preferences', async (req, res) => {
|
|
4007
|
+
if (!guard(req, res))
|
|
4008
|
+
return;
|
|
4009
|
+
const body = req.body ?? {};
|
|
4010
|
+
try {
|
|
4011
|
+
for (const [key, value] of Object.entries(body)) {
|
|
4012
|
+
await this.architect.updatePreference(key, value);
|
|
4013
|
+
}
|
|
4014
|
+
audit('personality.preferences.updated', { keys: Object.keys(body) });
|
|
4015
|
+
res.json({ ok: true });
|
|
4016
|
+
}
|
|
4017
|
+
catch (err) {
|
|
4018
|
+
res.status(500).json({ error: err.message ?? 'Failed to update preferences' });
|
|
4019
|
+
}
|
|
4020
|
+
});
|
|
4021
|
+
// --- Feedback insights ---
|
|
4022
|
+
router.get('/feedback/insights', (req, res) => {
|
|
4023
|
+
if (!guard(req, res))
|
|
4024
|
+
return;
|
|
4025
|
+
try {
|
|
4026
|
+
const insights = this.architect.getFeedbackInsights();
|
|
4027
|
+
res.json(insights);
|
|
4028
|
+
}
|
|
4029
|
+
catch (err) {
|
|
4030
|
+
res.status(500).json({ error: err.message ?? 'Failed to get feedback insights' });
|
|
4031
|
+
}
|
|
4032
|
+
});
|
|
4033
|
+
// --- User model ---
|
|
4034
|
+
router.get('/user-model', (_req, res) => {
|
|
4035
|
+
if (!guard(_req, res))
|
|
4036
|
+
return;
|
|
4037
|
+
const model = this.getCachedUserModel();
|
|
4038
|
+
if (!model) {
|
|
4039
|
+
res.status(404).json({ error: 'User model not available' });
|
|
4040
|
+
return;
|
|
4041
|
+
}
|
|
4042
|
+
res.json(model);
|
|
4043
|
+
});
|
|
4044
|
+
// --- Corrections (Gap 8) ---
|
|
4045
|
+
router.post('/corrections', async (req, res) => {
|
|
4046
|
+
if (!guard(req, res))
|
|
4047
|
+
return;
|
|
4048
|
+
const { userMessage, detectedDomain, correctedDomain } = req.body ?? {};
|
|
4049
|
+
if (!userMessage || !detectedDomain || !correctedDomain) {
|
|
4050
|
+
res.status(400).json({ error: 'Missing required fields: userMessage, detectedDomain, correctedDomain' });
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
try {
|
|
4054
|
+
await this.architect.recordCorrection(userMessage, detectedDomain, correctedDomain);
|
|
4055
|
+
audit('personality.correction', { detectedDomain, correctedDomain });
|
|
4056
|
+
res.status(201).json({ ok: true });
|
|
4057
|
+
}
|
|
4058
|
+
catch (err) {
|
|
4059
|
+
res.status(500).json({ error: err.message ?? 'Failed to record correction' });
|
|
4060
|
+
}
|
|
4061
|
+
});
|
|
4062
|
+
router.get('/corrections/stats', (req, res) => {
|
|
4063
|
+
if (!guard(req, res))
|
|
4064
|
+
return;
|
|
4065
|
+
try {
|
|
4066
|
+
const stats = this.architect.getCorrectionStats();
|
|
4067
|
+
res.json(stats);
|
|
4068
|
+
}
|
|
4069
|
+
catch (err) {
|
|
4070
|
+
res.status(500).json({ error: err.message ?? 'Failed to get correction stats' });
|
|
4071
|
+
}
|
|
4072
|
+
});
|
|
4073
|
+
// --- Data portability (Gap 10) ---
|
|
4074
|
+
router.get('/export', async (req, res) => {
|
|
4075
|
+
if (!guard(req, res))
|
|
4076
|
+
return;
|
|
4077
|
+
try {
|
|
4078
|
+
const data = await this.architect.exportData();
|
|
4079
|
+
audit('personality.data.exported', {});
|
|
4080
|
+
res.set('Content-Type', 'application/json');
|
|
4081
|
+
res.send(data);
|
|
4082
|
+
}
|
|
4083
|
+
catch (err) {
|
|
4084
|
+
res.status(500).json({ error: err.message ?? 'Failed to export data' });
|
|
4085
|
+
}
|
|
4086
|
+
});
|
|
4087
|
+
router.delete('/data', async (req, res) => {
|
|
4088
|
+
if (!guard(req, res))
|
|
4089
|
+
return;
|
|
4090
|
+
try {
|
|
4091
|
+
await this.architect.clearAllData();
|
|
4092
|
+
audit('personality.data.cleared', {});
|
|
4093
|
+
res.json({ ok: true });
|
|
4094
|
+
}
|
|
4095
|
+
catch (err) {
|
|
4096
|
+
res.status(500).json({ error: err.message ?? 'Failed to clear data' });
|
|
4097
|
+
}
|
|
4098
|
+
});
|
|
4099
|
+
// --- Conversation export (Gap 9 / Task 7) ---
|
|
4100
|
+
router.get('/sessions/:sessionId/export', (req, res) => {
|
|
4101
|
+
if (!guard(req, res))
|
|
4102
|
+
return;
|
|
4103
|
+
const format = req.query.format || 'json';
|
|
4104
|
+
if (!['json', 'markdown', 'csv'].includes(format)) {
|
|
4105
|
+
res.status(400).json({ error: 'Invalid format. Must be json, markdown, or csv' });
|
|
4106
|
+
return;
|
|
4107
|
+
}
|
|
4108
|
+
try {
|
|
4109
|
+
const msgs = this.sessions.getMessages(req.params.sessionId);
|
|
4110
|
+
const chatMessages = msgs
|
|
4111
|
+
.filter((m) => m.role === 'user' || m.role === 'assistant')
|
|
4112
|
+
.map((m) => ({
|
|
4113
|
+
role: m.role,
|
|
4114
|
+
content: m.content,
|
|
4115
|
+
timestamp: m.timestamp,
|
|
4116
|
+
metadata: m.metadata,
|
|
4117
|
+
}));
|
|
4118
|
+
const exported = this.architect.exportConversationAs(chatMessages, req.params.sessionId, format);
|
|
4119
|
+
if (format === 'json') {
|
|
4120
|
+
res.set('Content-Type', 'application/json');
|
|
4121
|
+
}
|
|
4122
|
+
else if (format === 'markdown') {
|
|
4123
|
+
res.set('Content-Type', 'text/markdown');
|
|
4124
|
+
}
|
|
4125
|
+
else {
|
|
4126
|
+
res.set('Content-Type', 'text/csv');
|
|
4127
|
+
}
|
|
4128
|
+
res.send(exported);
|
|
4129
|
+
}
|
|
4130
|
+
catch (err) {
|
|
4131
|
+
res.status(500).json({ error: err.message ?? 'Failed to export conversation' });
|
|
4132
|
+
}
|
|
4133
|
+
});
|
|
4134
|
+
// --- Feedback REST (Task 9) ---
|
|
4135
|
+
router.post('/sessions/:sessionId/messages/:messageId/feedback', async (req, res) => {
|
|
4136
|
+
if (!guard(req, res))
|
|
4137
|
+
return;
|
|
4138
|
+
const { rating, note } = req.body ?? {};
|
|
4139
|
+
if (!rating || !['up', 'down'].includes(rating)) {
|
|
4140
|
+
res.status(400).json({ error: 'Missing or invalid rating. Must be "up" or "down"' });
|
|
4141
|
+
return;
|
|
4142
|
+
}
|
|
4143
|
+
try {
|
|
4144
|
+
let domain = 'general';
|
|
4145
|
+
const msgs = this.sessions.getMessages(req.params.sessionId);
|
|
4146
|
+
const msg = msgs.find((m) => m.id === req.params.messageId);
|
|
4147
|
+
if (msg?.metadata?.architectDomain) {
|
|
4148
|
+
domain = msg.metadata.architectDomain;
|
|
4149
|
+
}
|
|
4150
|
+
const mapped = rating === 'up' ? 'helpful' : 'off_target';
|
|
4151
|
+
await this.architect.recordFeedback({
|
|
4152
|
+
domain: domain,
|
|
4153
|
+
rating: mapped,
|
|
4154
|
+
note,
|
|
4155
|
+
});
|
|
4156
|
+
audit('personality.feedback', {
|
|
4157
|
+
sessionId: req.params.sessionId,
|
|
4158
|
+
messageId: req.params.messageId,
|
|
4159
|
+
rating,
|
|
4160
|
+
});
|
|
4161
|
+
res.status(201).json({ ok: true });
|
|
4162
|
+
}
|
|
4163
|
+
catch (err) {
|
|
4164
|
+
res.status(500).json({ error: err.message ?? 'Failed to record feedback' });
|
|
4165
|
+
}
|
|
4166
|
+
});
|
|
4167
|
+
return router;
|
|
4168
|
+
}
|
|
4169
|
+
async runResearchJob(job, client) {
|
|
4170
|
+
const onProgress = (event) => {
|
|
4171
|
+
job.progress.push(event);
|
|
4172
|
+
this.sendToClient(client, { type: 'research_progress', payload: { jobId: job.id, ...event } });
|
|
4173
|
+
};
|
|
4174
|
+
const provider = this.providers.getPrimaryProvider();
|
|
4175
|
+
// NOTE: DeepResearchOrchestrator does not currently accept a DocumentStore parameter.
|
|
4176
|
+
// When it gains that support, pass this.documentStore here.
|
|
4177
|
+
const orchestrator = new DeepResearchOrchestrator(provider, undefined, this.researchEngine);
|
|
4178
|
+
const result = await orchestrator.research(job.question, job.depth, onProgress);
|
|
4179
|
+
if (job.depth === 'deep') {
|
|
4180
|
+
const reportGen = new ReportGenerator(provider);
|
|
4181
|
+
job.report = await reportGen.generateReport({
|
|
4182
|
+
...result,
|
|
4183
|
+
question: job.question,
|
|
4184
|
+
depth: job.depth,
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
job.status = 'completed';
|
|
4188
|
+
job.completedAt = Date.now();
|
|
4189
|
+
await audit('research.completed', {
|
|
4190
|
+
jobId: job.id,
|
|
4191
|
+
sourceCount: result.sources.length,
|
|
4192
|
+
duration: job.completedAt - job.createdAt,
|
|
4193
|
+
});
|
|
4194
|
+
this.sendToClient(client, { type: 'research_completed', payload: { jobId: job.id } });
|
|
4195
|
+
}
|
|
4196
|
+
createResearchRouter() {
|
|
4197
|
+
const router = Router();
|
|
4198
|
+
const self = this;
|
|
4199
|
+
router.post('/', (req, res) => {
|
|
4200
|
+
const { question, depth = 'deep' } = req.body;
|
|
4201
|
+
if (!question || typeof question !== 'string') {
|
|
4202
|
+
return res.status(400).json({ error: 'question required' });
|
|
4203
|
+
}
|
|
4204
|
+
const job = {
|
|
4205
|
+
id: crypto.randomUUID(),
|
|
4206
|
+
question,
|
|
4207
|
+
depth,
|
|
4208
|
+
status: 'planning',
|
|
4209
|
+
createdAt: Date.now(),
|
|
4210
|
+
progress: [],
|
|
4211
|
+
};
|
|
4212
|
+
self.researchJobs.set(job.id, job);
|
|
4213
|
+
audit('research.started', { jobId: job.id, question: job.question, depth: job.depth });
|
|
4214
|
+
res.status(202).json({ jobId: job.id, status: job.status });
|
|
4215
|
+
});
|
|
4216
|
+
router.get('/', (_req, res) => {
|
|
4217
|
+
const limit = Number(_req.query.limit) || 20;
|
|
4218
|
+
const offset = Number(_req.query.offset) || 0;
|
|
4219
|
+
const all = [...self.researchJobs.values()].sort((a, b) => b.createdAt - a.createdAt);
|
|
4220
|
+
res.json({ jobs: all.slice(offset, offset + limit), total: all.length });
|
|
4221
|
+
});
|
|
4222
|
+
router.get('/:jobId', (req, res) => {
|
|
4223
|
+
const job = self.researchJobs.get(req.params.jobId);
|
|
4224
|
+
if (!job)
|
|
4225
|
+
return res.status(404).json({ error: 'not found' });
|
|
4226
|
+
res.json(job);
|
|
4227
|
+
});
|
|
4228
|
+
router.delete('/:jobId', (req, res) => {
|
|
4229
|
+
const job = self.researchJobs.get(req.params.jobId);
|
|
4230
|
+
if (!job)
|
|
4231
|
+
return res.status(404).json({ error: 'not found' });
|
|
4232
|
+
if (job.status === 'completed' || job.status === 'failed') {
|
|
4233
|
+
return res.status(409).json({ error: 'job already finished' });
|
|
4234
|
+
}
|
|
4235
|
+
job.status = 'cancelled';
|
|
4236
|
+
audit('research.cancelled', { jobId: job.id });
|
|
4237
|
+
res.json({ jobId: job.id, status: 'cancelled' });
|
|
4238
|
+
});
|
|
4239
|
+
router.get('/:jobId/sources', (req, res) => {
|
|
4240
|
+
const job = self.researchJobs.get(req.params.jobId);
|
|
4241
|
+
if (!job)
|
|
4242
|
+
return res.status(404).json({ error: 'not found' });
|
|
4243
|
+
res.json({ sources: job.report?.sources ?? [] });
|
|
4244
|
+
});
|
|
4245
|
+
return router;
|
|
4246
|
+
}
|
|
4247
|
+
createAmbientRouter() {
|
|
4248
|
+
const router = Router();
|
|
4249
|
+
const self = this;
|
|
4250
|
+
function guard(res) {
|
|
4251
|
+
if (!self.ambientEngine || !self.ambientNotifications) {
|
|
4252
|
+
res.status(503).json({ error: 'Ambient system not available' });
|
|
4253
|
+
return false;
|
|
4254
|
+
}
|
|
4255
|
+
return true;
|
|
4256
|
+
}
|
|
4257
|
+
// Pattern management
|
|
4258
|
+
router.get('/patterns', (_req, res) => {
|
|
4259
|
+
if (!guard(res))
|
|
4260
|
+
return;
|
|
4261
|
+
res.json({ patterns: self.ambientEngine.getPatterns() });
|
|
4262
|
+
});
|
|
4263
|
+
router.get('/patterns/:id', (req, res) => {
|
|
4264
|
+
if (!guard(res))
|
|
4265
|
+
return;
|
|
4266
|
+
const pattern = self.ambientEngine.getPattern(req.params.id);
|
|
4267
|
+
if (!pattern)
|
|
4268
|
+
return res.status(404).json({ error: 'Pattern not found' });
|
|
4269
|
+
res.json(pattern);
|
|
4270
|
+
});
|
|
4271
|
+
router.post('/patterns/detect', async (_req, res) => {
|
|
4272
|
+
if (!guard(res))
|
|
4273
|
+
return;
|
|
4274
|
+
const detected = self.ambientEngine.detectPatterns();
|
|
4275
|
+
await audit('ambient.patterns.detected', { count: detected.length });
|
|
4276
|
+
res.json({ detected: detected.length });
|
|
4277
|
+
});
|
|
4278
|
+
router.delete('/patterns', async (_req, res) => {
|
|
4279
|
+
if (!guard(res))
|
|
4280
|
+
return;
|
|
4281
|
+
self.ambientEngine.reset();
|
|
4282
|
+
await audit('ambient.patterns.reset', {});
|
|
4283
|
+
res.json({ ok: true });
|
|
4284
|
+
});
|
|
4285
|
+
// Anticipations
|
|
4286
|
+
router.get('/anticipations', (_req, res) => {
|
|
4287
|
+
if (!self.anticipationEngine)
|
|
4288
|
+
return res.status(503).json({ error: 'Anticipation engine not available' });
|
|
4289
|
+
res.json({ anticipations: self.anticipationEngine.getAnticipations() });
|
|
4290
|
+
});
|
|
4291
|
+
// Notifications
|
|
4292
|
+
router.get('/notifications', (req, res) => {
|
|
4293
|
+
if (!guard(res))
|
|
4294
|
+
return;
|
|
4295
|
+
const priority = req.query.priority;
|
|
4296
|
+
const items = priority
|
|
4297
|
+
? self.ambientNotifications.getByPriority(priority)
|
|
4298
|
+
: self.ambientNotifications.getQueue();
|
|
4299
|
+
res.json({ notifications: items });
|
|
4300
|
+
});
|
|
4301
|
+
router.post('/notifications/:id/dismiss', (req, res) => {
|
|
4302
|
+
if (!guard(res))
|
|
4303
|
+
return;
|
|
4304
|
+
const ok = self.ambientNotifications.dismiss(req.params.id);
|
|
4305
|
+
if (!ok)
|
|
4306
|
+
return res.status(404).json({ error: 'Notification not found' });
|
|
4307
|
+
res.json({ ok: true });
|
|
4308
|
+
});
|
|
4309
|
+
router.get('/notifications/stats', (_req, res) => {
|
|
4310
|
+
if (!guard(res))
|
|
4311
|
+
return;
|
|
4312
|
+
res.json({ pending: self.ambientNotifications.getPendingCount() });
|
|
4313
|
+
});
|
|
4314
|
+
// Scheduler control
|
|
4315
|
+
router.get('/scheduler/status', (_req, res) => {
|
|
4316
|
+
if (!self.ambientScheduler)
|
|
4317
|
+
return res.status(503).json({ error: 'Scheduler not available' });
|
|
4318
|
+
res.json({ running: self.ambientScheduler.isRunning(), config: self.ambientScheduler.getConfig() });
|
|
4319
|
+
});
|
|
4320
|
+
router.post('/scheduler/start', async (_req, res) => {
|
|
4321
|
+
if (!self.ambientScheduler)
|
|
4322
|
+
return res.status(503).json({ error: 'Scheduler not available' });
|
|
4323
|
+
self.ambientScheduler.start();
|
|
4324
|
+
await audit('ambient.scheduler.started', {});
|
|
4325
|
+
res.json({ ok: true });
|
|
4326
|
+
});
|
|
4327
|
+
router.post('/scheduler/stop', async (_req, res) => {
|
|
4328
|
+
if (!self.ambientScheduler)
|
|
4329
|
+
return res.status(503).json({ error: 'Scheduler not available' });
|
|
4330
|
+
self.ambientScheduler.stop();
|
|
4331
|
+
await audit('ambient.scheduler.stopped', {});
|
|
4332
|
+
res.json({ ok: true });
|
|
4333
|
+
});
|
|
4334
|
+
router.put('/scheduler/config', (_req, res) => {
|
|
4335
|
+
if (!self.ambientScheduler)
|
|
4336
|
+
return res.status(503).json({ error: 'Scheduler not available' });
|
|
4337
|
+
res.json({ config: self.ambientScheduler.getConfig() });
|
|
4338
|
+
});
|
|
4339
|
+
return router;
|
|
4340
|
+
}
|
|
4341
|
+
createVoiceRouter() {
|
|
4342
|
+
const router = Router();
|
|
4343
|
+
router.get('/status', (_req, res) => {
|
|
4344
|
+
try {
|
|
4345
|
+
if (!this.voiceManager) {
|
|
4346
|
+
return res.json({ enabled: false });
|
|
4347
|
+
}
|
|
4348
|
+
res.json({ enabled: true });
|
|
4349
|
+
}
|
|
4350
|
+
catch (err) {
|
|
4351
|
+
res.status(500).json({ error: err.message });
|
|
4352
|
+
}
|
|
4353
|
+
});
|
|
4354
|
+
router.get('/sessions/:clientId', (req, res) => {
|
|
4355
|
+
try {
|
|
4356
|
+
if (!this.voiceManager) {
|
|
4357
|
+
return res.status(503).json({ error: 'Voice not initialized' });
|
|
4358
|
+
}
|
|
4359
|
+
const active = this.voiceManager.hasActiveSession(req.params.clientId);
|
|
4360
|
+
res.json({ active });
|
|
4361
|
+
}
|
|
4362
|
+
catch (err) {
|
|
4363
|
+
res.status(500).json({ error: err.message });
|
|
4364
|
+
}
|
|
4365
|
+
});
|
|
4366
|
+
return router;
|
|
4367
|
+
}
|
|
4368
|
+
createWebhooksRouter() {
|
|
4369
|
+
const router = Router();
|
|
4370
|
+
router.get('/', async (_req, res) => {
|
|
4371
|
+
if (!this.webhookManager)
|
|
4372
|
+
return res.status(503).json({ error: 'Webhooks not configured' });
|
|
4373
|
+
try {
|
|
4374
|
+
const webhooks = await this.webhookManager.list();
|
|
4375
|
+
res.json({ webhooks });
|
|
4376
|
+
}
|
|
4377
|
+
catch (err) {
|
|
4378
|
+
res.status(500).json({ error: err.message });
|
|
4379
|
+
}
|
|
4380
|
+
});
|
|
4381
|
+
router.post('/', async (req, res) => {
|
|
4382
|
+
if (!this.webhookManager)
|
|
4383
|
+
return res.status(503).json({ error: 'Webhooks not configured' });
|
|
4384
|
+
try {
|
|
4385
|
+
const webhook = await this.webhookManager.create(req.body);
|
|
4386
|
+
res.json(webhook);
|
|
4387
|
+
}
|
|
4388
|
+
catch (err) {
|
|
4389
|
+
res.status(500).json({ error: err.message });
|
|
4390
|
+
}
|
|
4391
|
+
});
|
|
4392
|
+
router.put('/:id', async (req, res) => {
|
|
4393
|
+
if (!this.webhookManager)
|
|
4394
|
+
return res.status(503).json({ error: 'Webhooks not configured' });
|
|
4395
|
+
try {
|
|
4396
|
+
const updated = await this.webhookManager.update(req.params.id, req.body);
|
|
4397
|
+
if (!updated)
|
|
4398
|
+
return res.status(404).json({ error: 'Webhook not found' });
|
|
4399
|
+
res.json(updated);
|
|
4400
|
+
}
|
|
4401
|
+
catch (err) {
|
|
4402
|
+
res.status(500).json({ error: err.message });
|
|
4403
|
+
}
|
|
4404
|
+
});
|
|
4405
|
+
router.delete('/:id', async (req, res) => {
|
|
4406
|
+
if (!this.webhookManager)
|
|
4407
|
+
return res.status(503).json({ error: 'Webhooks not configured' });
|
|
4408
|
+
try {
|
|
4409
|
+
const deleted = await this.webhookManager.delete(req.params.id);
|
|
4410
|
+
if (!deleted)
|
|
4411
|
+
return res.status(404).json({ error: 'Webhook not found' });
|
|
4412
|
+
res.json({ deleted: true });
|
|
4413
|
+
}
|
|
4414
|
+
catch (err) {
|
|
4415
|
+
res.status(500).json({ error: err.message });
|
|
4416
|
+
}
|
|
4417
|
+
});
|
|
4418
|
+
return router;
|
|
4419
|
+
}
|
|
4420
|
+
createConsciousnessRouter() {
|
|
4421
|
+
const router = Router();
|
|
4422
|
+
const self = this;
|
|
4423
|
+
router.get('/pulse', (_req, res) => {
|
|
4424
|
+
if (!self.consciousness)
|
|
4425
|
+
return res.status(503).json({ error: 'Consciousness not initialized' });
|
|
4426
|
+
try {
|
|
4427
|
+
const pulse = self.consciousness.monitor.getPulse();
|
|
4428
|
+
res.json(pulse);
|
|
4429
|
+
}
|
|
4430
|
+
catch (err) {
|
|
4431
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4432
|
+
}
|
|
4433
|
+
});
|
|
4434
|
+
router.get('/self-model', async (_req, res) => {
|
|
4435
|
+
if (!self.consciousness)
|
|
4436
|
+
return res.status(503).json({ error: 'Consciousness not initialized' });
|
|
4437
|
+
try {
|
|
4438
|
+
const snapshot = await self.consciousness.model.synthesize();
|
|
4439
|
+
res.json(snapshot);
|
|
4440
|
+
}
|
|
4441
|
+
catch (err) {
|
|
4442
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4443
|
+
}
|
|
4444
|
+
});
|
|
4445
|
+
router.get('/journal/sessions', async (req, res) => {
|
|
4446
|
+
if (!self.consciousness)
|
|
4447
|
+
return res.status(503).json({ error: 'Consciousness not initialized' });
|
|
4448
|
+
try {
|
|
4449
|
+
const limit = Number(req.query.limit) || 10;
|
|
4450
|
+
const sessions = await self.consciousness.journal.getRecentSessions(limit);
|
|
4451
|
+
res.json(sessions);
|
|
4452
|
+
}
|
|
4453
|
+
catch (err) {
|
|
4454
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4455
|
+
}
|
|
4456
|
+
});
|
|
4457
|
+
router.get('/journal/sessions/:sessionId', async (req, res) => {
|
|
4458
|
+
if (!self.consciousness)
|
|
4459
|
+
return res.status(503).json({ error: 'Consciousness not initialized' });
|
|
4460
|
+
try {
|
|
4461
|
+
const session = await self.consciousness.journal.getSession(req.params.sessionId);
|
|
4462
|
+
res.json(session);
|
|
4463
|
+
}
|
|
4464
|
+
catch (err) {
|
|
4465
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4466
|
+
}
|
|
4467
|
+
});
|
|
4468
|
+
router.get('/repairs', async (req, res) => {
|
|
4469
|
+
if (!self.consciousness)
|
|
4470
|
+
return res.status(503).json({ error: 'Consciousness not initialized' });
|
|
4471
|
+
try {
|
|
4472
|
+
const limit = Number(req.query.limit) || 20;
|
|
4473
|
+
const history = await self.consciousness.repair.getRepairHistory(limit);
|
|
4474
|
+
res.json(history);
|
|
4475
|
+
}
|
|
4476
|
+
catch (err) {
|
|
4477
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4478
|
+
}
|
|
4479
|
+
});
|
|
4480
|
+
router.get('/repairs/pending', async (_req, res) => {
|
|
4481
|
+
if (!self.consciousness)
|
|
4482
|
+
return res.status(503).json({ error: 'Consciousness not initialized' });
|
|
4483
|
+
try {
|
|
4484
|
+
const pending = await self.consciousness.repair.getPendingApprovals();
|
|
4485
|
+
res.json(pending);
|
|
4486
|
+
}
|
|
4487
|
+
catch (err) {
|
|
4488
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4489
|
+
}
|
|
4490
|
+
});
|
|
4491
|
+
return router;
|
|
4492
|
+
}
|
|
4493
|
+
createAgentProtocolRouter() {
|
|
4494
|
+
const router = Router();
|
|
4495
|
+
router.get('/identity', (_req, res) => {
|
|
4496
|
+
if (!this.agentProtocol || !this.agentDirectory) {
|
|
4497
|
+
return res.status(503).json({ error: 'Agent protocol not initialized' });
|
|
4498
|
+
}
|
|
4499
|
+
try {
|
|
4500
|
+
const identity = this.agentProtocol.getIdentity();
|
|
4501
|
+
res.json(identity);
|
|
4502
|
+
}
|
|
4503
|
+
catch (err) {
|
|
4504
|
+
res.status(500).json({ error: err.message });
|
|
4505
|
+
}
|
|
4506
|
+
});
|
|
4507
|
+
router.get('/inbox', (_req, res) => {
|
|
4508
|
+
if (!this.agentProtocol || !this.agentDirectory) {
|
|
4509
|
+
return res.status(503).json({ error: 'Agent protocol not initialized' });
|
|
4510
|
+
}
|
|
4511
|
+
try {
|
|
4512
|
+
const limit = _req.query.limit ? parseInt(_req.query.limit, 10) : 50;
|
|
4513
|
+
const messages = this.agentProtocol.getInbox(limit);
|
|
4514
|
+
res.json({ messages });
|
|
4515
|
+
}
|
|
4516
|
+
catch (err) {
|
|
4517
|
+
res.status(500).json({ error: err.message });
|
|
4518
|
+
}
|
|
4519
|
+
});
|
|
4520
|
+
router.post('/messages', async (req, res) => {
|
|
4521
|
+
if (!this.agentProtocol || !this.agentDirectory) {
|
|
4522
|
+
return res.status(503).json({ error: 'Agent protocol not initialized' });
|
|
4523
|
+
}
|
|
4524
|
+
try {
|
|
4525
|
+
const { to, type, payload, replyTo } = req.body;
|
|
4526
|
+
const message = await this.agentProtocol.send(to, type, payload, replyTo);
|
|
4527
|
+
res.json(message);
|
|
4528
|
+
}
|
|
4529
|
+
catch (err) {
|
|
4530
|
+
res.status(500).json({ error: err.message });
|
|
4531
|
+
}
|
|
4532
|
+
});
|
|
4533
|
+
router.post('/receive', async (req, res) => {
|
|
4534
|
+
if (!this.agentProtocol || !this.agentDirectory) {
|
|
4535
|
+
return res.status(503).json({ error: 'Agent protocol not initialized' });
|
|
4536
|
+
}
|
|
4537
|
+
try {
|
|
4538
|
+
const response = await this.agentProtocol.receive(req.body);
|
|
4539
|
+
res.json(response ?? { accepted: true });
|
|
4540
|
+
}
|
|
4541
|
+
catch (err) {
|
|
4542
|
+
res.status(500).json({ error: err.message });
|
|
4543
|
+
}
|
|
4544
|
+
});
|
|
4545
|
+
router.get('/directory', async (_req, res) => {
|
|
4546
|
+
if (!this.agentProtocol || !this.agentDirectory) {
|
|
4547
|
+
return res.status(503).json({ error: 'Agent protocol not initialized' });
|
|
4548
|
+
}
|
|
4549
|
+
try {
|
|
4550
|
+
const agents = await this.agentDirectory.listAll();
|
|
4551
|
+
res.json({ agents });
|
|
4552
|
+
}
|
|
4553
|
+
catch (err) {
|
|
4554
|
+
res.status(500).json({ error: err.message });
|
|
4555
|
+
}
|
|
4556
|
+
});
|
|
4557
|
+
router.get('/directory/search', async (req, res) => {
|
|
4558
|
+
if (!this.agentProtocol || !this.agentDirectory) {
|
|
4559
|
+
return res.status(503).json({ error: 'Agent protocol not initialized' });
|
|
4560
|
+
}
|
|
4561
|
+
try {
|
|
4562
|
+
const results = await this.agentDirectory.search(req.query.q);
|
|
4563
|
+
res.json({ results });
|
|
4564
|
+
}
|
|
4565
|
+
catch (err) {
|
|
4566
|
+
res.status(500).json({ error: err.message });
|
|
4567
|
+
}
|
|
4568
|
+
});
|
|
4569
|
+
return router;
|
|
4570
|
+
}
|
|
4571
|
+
createTrustRouter() {
|
|
4572
|
+
const router = Router();
|
|
4573
|
+
const self = this;
|
|
4574
|
+
router.get('/levels', (_req, res) => {
|
|
4575
|
+
if (!self.trustEngine)
|
|
4576
|
+
return res.status(503).json({ error: 'Trust engine not initialized' });
|
|
4577
|
+
try {
|
|
4578
|
+
const levels = self.trustEngine.getAllLevels();
|
|
4579
|
+
res.json({ levels });
|
|
4580
|
+
}
|
|
4581
|
+
catch (err) {
|
|
4582
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4583
|
+
}
|
|
4584
|
+
});
|
|
4585
|
+
router.get('/levels/:domain', (_req, res) => {
|
|
4586
|
+
if (!self.trustEngine)
|
|
4587
|
+
return res.status(503).json({ error: 'Trust engine not initialized' });
|
|
4588
|
+
try {
|
|
4589
|
+
const domain = _req.params.domain;
|
|
4590
|
+
const level = self.trustEngine.getTrustLevel(domain);
|
|
4591
|
+
const evidence = self.trustEngine.getEvidence(domain);
|
|
4592
|
+
res.json({ domain, level, evidence });
|
|
4593
|
+
}
|
|
4594
|
+
catch (err) {
|
|
4595
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4596
|
+
}
|
|
4597
|
+
});
|
|
4598
|
+
router.put('/levels/:domain', async (req, res) => {
|
|
4599
|
+
if (!self.trustEngine)
|
|
4600
|
+
return res.status(503).json({ error: 'Trust engine not initialized' });
|
|
4601
|
+
try {
|
|
4602
|
+
const domain = req.params.domain;
|
|
4603
|
+
const { level, reason } = req.body;
|
|
4604
|
+
await self.trustEngine.setTrustLevel(domain, level, reason);
|
|
4605
|
+
res.json({ success: true });
|
|
4606
|
+
}
|
|
4607
|
+
catch (err) {
|
|
4608
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4609
|
+
}
|
|
4610
|
+
});
|
|
4611
|
+
router.get('/audit', (_req, res) => {
|
|
4612
|
+
if (!self.trustEngine)
|
|
4613
|
+
return res.status(503).json({ error: 'Trust engine not initialized' });
|
|
4614
|
+
try {
|
|
4615
|
+
const history = self.trustAuditTrail ? self.trustAuditTrail.getAll() : [];
|
|
4616
|
+
res.json({ history });
|
|
4617
|
+
}
|
|
4618
|
+
catch (err) {
|
|
4619
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4620
|
+
}
|
|
4621
|
+
});
|
|
4622
|
+
return router;
|
|
4623
|
+
}
|
|
4624
|
+
createWorkflowRouter() {
|
|
4625
|
+
const router = Router();
|
|
4626
|
+
const self = this;
|
|
4627
|
+
// Static routes MUST come before parameterized /:id routes
|
|
4628
|
+
// GET / — list all workflows
|
|
4629
|
+
router.get('/', async (_req, res) => {
|
|
4630
|
+
if (!self.workflowEngine)
|
|
4631
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4632
|
+
try {
|
|
4633
|
+
const workflows = await self.workflowEngine.listAll();
|
|
4634
|
+
res.json({ workflows });
|
|
4635
|
+
}
|
|
4636
|
+
catch (err) {
|
|
4637
|
+
res.status(500).json({ error: err.message });
|
|
4638
|
+
}
|
|
4639
|
+
});
|
|
4640
|
+
// POST / — create workflow
|
|
4641
|
+
router.post('/', async (req, res) => {
|
|
4642
|
+
if (!self.workflowEngine)
|
|
4643
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4644
|
+
try {
|
|
4645
|
+
const workflow = await self.workflowEngine.createWorkflow(req.body);
|
|
4646
|
+
res.json(workflow);
|
|
4647
|
+
}
|
|
4648
|
+
catch (err) {
|
|
4649
|
+
res.status(500).json({ error: err.message });
|
|
4650
|
+
}
|
|
4651
|
+
});
|
|
4652
|
+
// GET /active — list active workflows
|
|
4653
|
+
router.get('/active', async (_req, res) => {
|
|
4654
|
+
if (!self.workflowEngine)
|
|
4655
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4656
|
+
try {
|
|
4657
|
+
const workflows = await self.workflowEngine.listActive();
|
|
4658
|
+
res.json({ workflows });
|
|
4659
|
+
}
|
|
4660
|
+
catch (err) {
|
|
4661
|
+
res.status(500).json({ error: err.message });
|
|
4662
|
+
}
|
|
4663
|
+
});
|
|
4664
|
+
// GET /approvals/pending — list pending approvals
|
|
4665
|
+
router.get('/approvals/pending', async (req, res) => {
|
|
4666
|
+
if (!self.approvalManager)
|
|
4667
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4668
|
+
try {
|
|
4669
|
+
const approvals = await self.approvalManager.getPending(req.query.userId);
|
|
4670
|
+
res.json({ approvals });
|
|
4671
|
+
}
|
|
4672
|
+
catch (err) {
|
|
4673
|
+
res.status(500).json({ error: err.message });
|
|
4674
|
+
}
|
|
4675
|
+
});
|
|
4676
|
+
// POST /approvals/:id/approve
|
|
4677
|
+
router.post('/approvals/:id/approve', async (req, res) => {
|
|
4678
|
+
if (!self.approvalManager)
|
|
4679
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4680
|
+
try {
|
|
4681
|
+
const approval = await self.approvalManager.approve(req.params.id, req.body.decidedBy, req.body.reason);
|
|
4682
|
+
if (!approval)
|
|
4683
|
+
return res.status(404).json({ error: 'Approval not found' });
|
|
4684
|
+
res.json(approval);
|
|
4685
|
+
}
|
|
4686
|
+
catch (err) {
|
|
4687
|
+
res.status(500).json({ error: err.message });
|
|
4688
|
+
}
|
|
4689
|
+
});
|
|
4690
|
+
// POST /approvals/:id/reject
|
|
4691
|
+
router.post('/approvals/:id/reject', async (req, res) => {
|
|
4692
|
+
if (!self.approvalManager)
|
|
4693
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4694
|
+
try {
|
|
4695
|
+
const rejection = await self.approvalManager.reject(req.params.id, req.body.decidedBy, req.body.reason);
|
|
4696
|
+
if (!rejection)
|
|
4697
|
+
return res.status(404).json({ error: 'Approval not found' });
|
|
4698
|
+
res.json(rejection);
|
|
4699
|
+
}
|
|
4700
|
+
catch (err) {
|
|
4701
|
+
res.status(500).json({ error: err.message });
|
|
4702
|
+
}
|
|
4703
|
+
});
|
|
4704
|
+
// GET /:id — get workflow by ID
|
|
4705
|
+
router.get('/:id', async (req, res) => {
|
|
4706
|
+
if (!self.workflowEngine)
|
|
4707
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4708
|
+
try {
|
|
4709
|
+
const workflow = await self.workflowEngine.getWorkflow(req.params.id);
|
|
4710
|
+
if (!workflow)
|
|
4711
|
+
return res.status(404).json({ error: 'Workflow not found' });
|
|
4712
|
+
res.json(workflow);
|
|
4713
|
+
}
|
|
4714
|
+
catch (err) {
|
|
4715
|
+
res.status(500).json({ error: err.message });
|
|
4716
|
+
}
|
|
4717
|
+
});
|
|
4718
|
+
// GET /:id/status — get workflow status
|
|
4719
|
+
router.get('/:id/status', async (req, res) => {
|
|
4720
|
+
if (!self.workflowEngine)
|
|
4721
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4722
|
+
try {
|
|
4723
|
+
const status = await self.workflowEngine.getStatus(req.params.id);
|
|
4724
|
+
if (!status)
|
|
4725
|
+
return res.status(404).json({ error: 'Workflow not found' });
|
|
4726
|
+
res.json(status);
|
|
4727
|
+
}
|
|
4728
|
+
catch (err) {
|
|
4729
|
+
res.status(500).json({ error: err.message });
|
|
4730
|
+
}
|
|
4731
|
+
});
|
|
4732
|
+
// POST /:id/start — start workflow
|
|
4733
|
+
router.post('/:id/start', async (req, res) => {
|
|
4734
|
+
if (!self.workflowEngine)
|
|
4735
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4736
|
+
try {
|
|
4737
|
+
const workflow = await self.workflowEngine.startWorkflow(req.params.id);
|
|
4738
|
+
if (!workflow)
|
|
4739
|
+
return res.status(404).json({ error: 'Workflow not found' });
|
|
4740
|
+
res.json(workflow);
|
|
4741
|
+
}
|
|
4742
|
+
catch (err) {
|
|
4743
|
+
res.status(500).json({ error: err.message });
|
|
4744
|
+
}
|
|
4745
|
+
});
|
|
4746
|
+
// POST /:id/cancel — cancel workflow
|
|
4747
|
+
router.post('/:id/cancel', async (req, res) => {
|
|
4748
|
+
if (!self.workflowEngine)
|
|
4749
|
+
return res.status(503).json({ error: 'Workflow engine not initialized' });
|
|
4750
|
+
try {
|
|
4751
|
+
const result = await self.workflowEngine.cancelWorkflow(req.params.id);
|
|
4752
|
+
if (!result)
|
|
4753
|
+
return res.status(404).json({ error: 'Workflow not found' });
|
|
4754
|
+
res.json({ cancelled: true });
|
|
4755
|
+
}
|
|
4756
|
+
catch (err) {
|
|
4757
|
+
res.status(500).json({ error: err.message });
|
|
4758
|
+
}
|
|
4759
|
+
});
|
|
4760
|
+
return router;
|
|
4761
|
+
}
|
|
4762
|
+
createUpdateRouter() {
|
|
4763
|
+
const router = Router();
|
|
4764
|
+
const self = this;
|
|
4765
|
+
// GET /status — installation info + current version
|
|
4766
|
+
router.get('/status', async (_req, res) => {
|
|
4767
|
+
if (!self.installationDetector || !self.versionChecker) {
|
|
4768
|
+
return res.status(503).json({ error: 'Update system not initialized' });
|
|
4769
|
+
}
|
|
4770
|
+
try {
|
|
4771
|
+
const info = self.installationDetector.detect();
|
|
4772
|
+
res.json({
|
|
4773
|
+
method: info.method,
|
|
4774
|
+
currentVersion: info.currentVersion,
|
|
4775
|
+
installPath: info.installPath,
|
|
4776
|
+
canSelfUpdate: info.canSelfUpdate,
|
|
4777
|
+
requiresSudo: info.requiresSudo,
|
|
4778
|
+
});
|
|
4779
|
+
}
|
|
4780
|
+
catch (err) {
|
|
4781
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4782
|
+
}
|
|
4783
|
+
});
|
|
4784
|
+
// POST /check — check for available updates
|
|
4785
|
+
router.post('/check', async (req, res) => {
|
|
4786
|
+
if (!self.installationDetector || !self.versionChecker) {
|
|
4787
|
+
return res.status(503).json({ error: 'Update system not initialized' });
|
|
4788
|
+
}
|
|
4789
|
+
try {
|
|
4790
|
+
const channel = (req.body?.channel ?? 'stable');
|
|
4791
|
+
const info = self.installationDetector.detect();
|
|
4792
|
+
const result = await self.versionChecker.check(info.currentVersion, channel);
|
|
4793
|
+
res.json(result);
|
|
4794
|
+
}
|
|
4795
|
+
catch (err) {
|
|
4796
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4797
|
+
}
|
|
4798
|
+
});
|
|
4799
|
+
// POST /apply — trigger an update
|
|
4800
|
+
router.post('/apply', async (req, res) => {
|
|
4801
|
+
if (!self.updater) {
|
|
4802
|
+
return res.status(503).json({ error: 'Update system not initialized' });
|
|
4803
|
+
}
|
|
4804
|
+
try {
|
|
4805
|
+
const channel = (req.body?.channel ?? 'stable');
|
|
4806
|
+
const result = await self.updater.update(channel);
|
|
4807
|
+
res.json(result);
|
|
4808
|
+
}
|
|
4809
|
+
catch (err) {
|
|
4810
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4811
|
+
}
|
|
4812
|
+
});
|
|
4813
|
+
// POST /rollback — rollback a staged update
|
|
4814
|
+
router.post('/rollback', async (_req, res) => {
|
|
4815
|
+
if (!self.updater) {
|
|
4816
|
+
return res.status(503).json({ error: 'Update system not initialized' });
|
|
4817
|
+
}
|
|
4818
|
+
try {
|
|
4819
|
+
await self.updater.rollback();
|
|
4820
|
+
res.json({ success: true });
|
|
4821
|
+
}
|
|
4822
|
+
catch (err) {
|
|
4823
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4824
|
+
}
|
|
4825
|
+
});
|
|
4826
|
+
return router;
|
|
4827
|
+
}
|
|
4828
|
+
createConnectorRouter() {
|
|
4829
|
+
const router = Router();
|
|
4830
|
+
// GET / — list all connectors
|
|
4831
|
+
router.get('/', (_req, res) => {
|
|
4832
|
+
if (!this.connectorRegistry)
|
|
4833
|
+
return res.status(503).json({ error: 'Connectors not configured' });
|
|
4834
|
+
try {
|
|
4835
|
+
res.json({ connectors: this.connectorRegistry.list() });
|
|
4836
|
+
}
|
|
4837
|
+
catch (err) {
|
|
4838
|
+
res.status(500).json({ error: err.message });
|
|
4839
|
+
}
|
|
4840
|
+
});
|
|
4841
|
+
// GET /:id — get connector by id
|
|
4842
|
+
router.get('/:id', (req, res) => {
|
|
4843
|
+
if (!this.connectorRegistry)
|
|
4844
|
+
return res.status(503).json({ error: 'Connectors not configured' });
|
|
4845
|
+
try {
|
|
4846
|
+
const connector = this.connectorRegistry.get(req.params.id);
|
|
4847
|
+
if (!connector)
|
|
4848
|
+
return res.status(404).json({ error: 'Connector not found' });
|
|
4849
|
+
res.json(connector);
|
|
4850
|
+
}
|
|
4851
|
+
catch (err) {
|
|
4852
|
+
res.status(500).json({ error: err.message });
|
|
4853
|
+
}
|
|
4854
|
+
});
|
|
4855
|
+
// GET /:id/actions — get actions for connector
|
|
4856
|
+
router.get('/:id/actions', (req, res) => {
|
|
4857
|
+
if (!this.connectorRegistry)
|
|
4858
|
+
return res.status(503).json({ error: 'Connectors not configured' });
|
|
4859
|
+
try {
|
|
4860
|
+
if (!this.connectorRegistry.has(req.params.id))
|
|
4861
|
+
return res.status(404).json({ error: 'Connector not found' });
|
|
4862
|
+
const actions = this.connectorRegistry.getActions(req.params.id);
|
|
4863
|
+
res.json({ actions });
|
|
4864
|
+
}
|
|
4865
|
+
catch (err) {
|
|
4866
|
+
res.status(500).json({ error: err.message });
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4869
|
+
// GET /:id/triggers — get triggers for connector
|
|
4870
|
+
router.get('/:id/triggers', (req, res) => {
|
|
4871
|
+
if (!this.connectorRegistry)
|
|
4872
|
+
return res.status(503).json({ error: 'Connectors not configured' });
|
|
4873
|
+
try {
|
|
4874
|
+
if (!this.connectorRegistry.has(req.params.id))
|
|
4875
|
+
return res.status(404).json({ error: 'Connector not found' });
|
|
4876
|
+
const triggers = this.connectorRegistry.getTriggers(req.params.id);
|
|
4877
|
+
res.json({ triggers });
|
|
4878
|
+
}
|
|
4879
|
+
catch (err) {
|
|
4880
|
+
res.status(500).json({ error: err.message });
|
|
4881
|
+
}
|
|
4882
|
+
});
|
|
4883
|
+
// POST /:id/authenticate — authenticate connector
|
|
4884
|
+
router.post('/:id/authenticate', async (req, res) => {
|
|
4885
|
+
if (!this.connectorRegistry)
|
|
4886
|
+
return res.status(503).json({ error: 'Connectors not configured' });
|
|
4887
|
+
if (!this.connectorAuthManager)
|
|
4888
|
+
return res.status(503).json({ error: 'Auth manager not configured' });
|
|
4889
|
+
try {
|
|
4890
|
+
if (!this.connectorRegistry.has(req.params.id))
|
|
4891
|
+
return res.status(404).json({ error: 'Connector not found' });
|
|
4892
|
+
await this.connectorAuthManager.authenticate(req.params.id, req.params.id, req.body.credentials);
|
|
4893
|
+
res.json({ authenticated: true });
|
|
4894
|
+
}
|
|
4895
|
+
catch (err) {
|
|
4896
|
+
res.status(500).json({ error: err.message });
|
|
4897
|
+
}
|
|
4898
|
+
});
|
|
4899
|
+
// POST /:id/disconnect — revoke connector token
|
|
4900
|
+
router.post('/:id/disconnect', async (req, res) => {
|
|
4901
|
+
if (!this.connectorRegistry)
|
|
4902
|
+
return res.status(503).json({ error: 'Connectors not configured' });
|
|
4903
|
+
if (!this.connectorAuthManager)
|
|
4904
|
+
return res.status(503).json({ error: 'Auth manager not configured' });
|
|
4905
|
+
try {
|
|
4906
|
+
if (!this.connectorRegistry.has(req.params.id))
|
|
4907
|
+
return res.status(404).json({ error: 'Connector not found' });
|
|
4908
|
+
await this.connectorAuthManager.revokeToken(req.params.id);
|
|
4909
|
+
res.json({ disconnected: true });
|
|
4910
|
+
}
|
|
4911
|
+
catch (err) {
|
|
4912
|
+
res.status(500).json({ error: err.message });
|
|
4913
|
+
}
|
|
4914
|
+
});
|
|
4915
|
+
// GET /:id/status — get connector auth status
|
|
4916
|
+
router.get('/:id/status', (req, res) => {
|
|
4917
|
+
if (!this.connectorRegistry)
|
|
4918
|
+
return res.status(503).json({ error: 'Connectors not configured' });
|
|
4919
|
+
if (!this.connectorAuthManager)
|
|
4920
|
+
return res.status(503).json({ error: 'Auth manager not configured' });
|
|
4921
|
+
try {
|
|
4922
|
+
if (!this.connectorRegistry.has(req.params.id))
|
|
4923
|
+
return res.status(404).json({ error: 'Connector not found' });
|
|
4924
|
+
res.json({
|
|
4925
|
+
connected: this.connectorAuthManager.hasToken(req.params.id),
|
|
4926
|
+
expired: this.connectorAuthManager.isTokenExpired(req.params.id),
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
catch (err) {
|
|
4930
|
+
res.status(500).json({ error: err.message });
|
|
4931
|
+
}
|
|
4932
|
+
});
|
|
4933
|
+
return router;
|
|
4934
|
+
}
|
|
4935
|
+
createImageRouter() {
|
|
4936
|
+
const router = Router();
|
|
4937
|
+
router.get('/providers', (_req, res) => {
|
|
4938
|
+
const providers = this.imageGenManager.listProviders();
|
|
4939
|
+
const details = providers.map((name) => {
|
|
4940
|
+
const p = this.imageGenManager.getProvider(name);
|
|
4941
|
+
return {
|
|
4942
|
+
name,
|
|
4943
|
+
supportedSizes: p?.supportedSizes ?? [],
|
|
4944
|
+
supportedFormats: p?.supportedFormats ?? [],
|
|
4945
|
+
defaultModel: p?.defaultModel ?? '',
|
|
4946
|
+
};
|
|
4947
|
+
});
|
|
4948
|
+
res.json({ providers: details });
|
|
4949
|
+
});
|
|
4950
|
+
router.post('/generate', async (req, res) => {
|
|
4951
|
+
const { prompt, size, format, provider, negativePrompt, count, model, seed, style } = req.body;
|
|
4952
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
4953
|
+
return res.status(400).json({ error: 'prompt required' });
|
|
4954
|
+
}
|
|
4955
|
+
const request = {
|
|
4956
|
+
prompt,
|
|
4957
|
+
...(size && { size }),
|
|
4958
|
+
...(format && { format }),
|
|
4959
|
+
...(provider && { provider }),
|
|
4960
|
+
...(negativePrompt && { negativePrompt }),
|
|
4961
|
+
...(count && { count }),
|
|
4962
|
+
...(model && { model }),
|
|
4963
|
+
...(seed !== undefined && { seed }),
|
|
4964
|
+
...(style && { style }),
|
|
4965
|
+
};
|
|
4966
|
+
const result = await this.imageGenManager.generate(request);
|
|
4967
|
+
if (!result.success) {
|
|
4968
|
+
return res.status(502).json({ error: result.error, durationMs: result.durationMs });
|
|
4969
|
+
}
|
|
4970
|
+
res.json(result);
|
|
4971
|
+
});
|
|
4972
|
+
return router;
|
|
4973
|
+
}
|
|
4974
|
+
createRagRouter() {
|
|
4975
|
+
const router = Router();
|
|
4976
|
+
router.get('/documents', (_req, res) => {
|
|
4977
|
+
res.json({ documents: this.documentStore.listDocuments() });
|
|
4978
|
+
});
|
|
4979
|
+
router.post('/documents', (req, res) => {
|
|
4980
|
+
const { title, content, type, metadata } = req.body;
|
|
4981
|
+
if (!title || typeof title !== 'string') {
|
|
4982
|
+
return res.status(400).json({ error: 'title required' });
|
|
4983
|
+
}
|
|
4984
|
+
if (!content || typeof content !== 'string') {
|
|
4985
|
+
return res.status(400).json({ error: 'content required' });
|
|
4986
|
+
}
|
|
4987
|
+
if (!type || typeof type !== 'string') {
|
|
4988
|
+
return res.status(400).json({ error: 'type required' });
|
|
4989
|
+
}
|
|
4990
|
+
const doc = this.documentStore.ingest(title, content, type, metadata);
|
|
4991
|
+
res.status(201).json(doc);
|
|
4992
|
+
});
|
|
4993
|
+
router.get('/documents/:id', (req, res) => {
|
|
4994
|
+
const doc = this.documentStore.getDocument(req.params.id);
|
|
4995
|
+
if (!doc)
|
|
4996
|
+
return res.status(404).json({ error: 'document not found' });
|
|
4997
|
+
res.json(doc);
|
|
4998
|
+
});
|
|
4999
|
+
router.delete('/documents/:id', (req, res) => {
|
|
5000
|
+
const doc = this.documentStore.getDocument(req.params.id);
|
|
5001
|
+
if (!doc)
|
|
5002
|
+
return res.status(404).json({ error: 'document not found' });
|
|
5003
|
+
this.documentStore.removeDocument(req.params.id);
|
|
5004
|
+
res.json({ deleted: true });
|
|
5005
|
+
});
|
|
5006
|
+
router.post('/search', (req, res) => {
|
|
5007
|
+
const { query, limit, minScore, type } = req.body;
|
|
5008
|
+
if (!query || typeof query !== 'string') {
|
|
5009
|
+
return res.status(400).json({ error: 'query required' });
|
|
5010
|
+
}
|
|
5011
|
+
const results = this.documentStore.search(query, { limit, minScore, type });
|
|
5012
|
+
res.json({ results });
|
|
5013
|
+
});
|
|
5014
|
+
router.post('/context', (req, res) => {
|
|
5015
|
+
const { query, maxTokens, maxChunks } = req.body;
|
|
5016
|
+
if (!query || typeof query !== 'string') {
|
|
5017
|
+
return res.status(400).json({ error: 'query required' });
|
|
5018
|
+
}
|
|
5019
|
+
const context = this.contextBuilder.buildContext(query, this.documentStore, { maxTokens, maxChunks });
|
|
5020
|
+
res.json({ context });
|
|
5021
|
+
});
|
|
5022
|
+
router.get('/stats', (_req, res) => {
|
|
5023
|
+
res.json(this.documentStore.stats());
|
|
5024
|
+
});
|
|
5025
|
+
return router;
|
|
5026
|
+
}
|
|
5027
|
+
createMcpRouter() {
|
|
5028
|
+
const router = Router();
|
|
5029
|
+
router.get('/servers', (_req, res) => {
|
|
5030
|
+
if (!this.mcpClientManager) {
|
|
5031
|
+
return res.json({ servers: {} });
|
|
5032
|
+
}
|
|
5033
|
+
const status = this.mcpClientManager.getStatus();
|
|
5034
|
+
const servers = {};
|
|
5035
|
+
for (const [name, s] of status) {
|
|
5036
|
+
servers[name] = s;
|
|
5037
|
+
}
|
|
5038
|
+
res.json({ servers });
|
|
5039
|
+
});
|
|
5040
|
+
router.post('/servers/:name/connect', async (req, res) => {
|
|
5041
|
+
if (!this.mcpClientManager) {
|
|
5042
|
+
return res.status(503).json({ error: 'MCP not configured' });
|
|
5043
|
+
}
|
|
5044
|
+
try {
|
|
5045
|
+
await this.mcpClientManager.connect(req.params.name);
|
|
5046
|
+
await audit('connector.connected', { connector: req.params.name, type: 'mcp' });
|
|
5047
|
+
res.json({ status: 'connected' });
|
|
5048
|
+
}
|
|
5049
|
+
catch (err) {
|
|
5050
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5051
|
+
}
|
|
5052
|
+
});
|
|
5053
|
+
router.post('/servers/:name/disconnect', async (req, res) => {
|
|
5054
|
+
if (!this.mcpClientManager) {
|
|
5055
|
+
return res.status(503).json({ error: 'MCP not configured' });
|
|
5056
|
+
}
|
|
5057
|
+
try {
|
|
5058
|
+
await this.mcpClientManager.disconnect(req.params.name);
|
|
5059
|
+
await audit('connector.disconnected', { connector: req.params.name, type: 'mcp' });
|
|
5060
|
+
res.json({ status: 'disconnected' });
|
|
5061
|
+
}
|
|
5062
|
+
catch (err) {
|
|
5063
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5064
|
+
}
|
|
5065
|
+
});
|
|
5066
|
+
router.get('/servers/:name/tools', (req, res) => {
|
|
5067
|
+
if (!this.mcpClientManager) {
|
|
5068
|
+
return res.status(503).json({ error: 'MCP not configured' });
|
|
5069
|
+
}
|
|
5070
|
+
const tools = this.mcpClientManager.getToolsForServer(req.params.name);
|
|
5071
|
+
res.json({ tools });
|
|
5072
|
+
});
|
|
5073
|
+
return router;
|
|
5074
|
+
}
|
|
5075
|
+
createEvalRouter() {
|
|
5076
|
+
const router = Router();
|
|
5077
|
+
// GET /history/:suiteName — returns suite run history
|
|
5078
|
+
router.get('/history/:suiteName', (_req, res) => {
|
|
5079
|
+
if (!this.evalStore) {
|
|
5080
|
+
return res.status(503).json({ error: 'Evaluation system not initialized' });
|
|
5081
|
+
}
|
|
5082
|
+
try {
|
|
5083
|
+
const history = this.evalStore.getHistory(_req.params.suiteName);
|
|
5084
|
+
res.json({ history });
|
|
5085
|
+
}
|
|
5086
|
+
catch (err) {
|
|
5087
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5088
|
+
}
|
|
5089
|
+
});
|
|
5090
|
+
// GET /latest/:suiteName — returns latest suite result or 404
|
|
5091
|
+
router.get('/latest/:suiteName', (_req, res) => {
|
|
5092
|
+
if (!this.evalStore) {
|
|
5093
|
+
return res.status(503).json({ error: 'Evaluation system not initialized' });
|
|
5094
|
+
}
|
|
5095
|
+
try {
|
|
5096
|
+
const latest = this.evalStore.getLatest(_req.params.suiteName);
|
|
5097
|
+
if (!latest) {
|
|
5098
|
+
return res.status(404).json({ error: 'No results found for suite' });
|
|
5099
|
+
}
|
|
5100
|
+
res.json(latest);
|
|
5101
|
+
}
|
|
5102
|
+
catch (err) {
|
|
5103
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5104
|
+
}
|
|
5105
|
+
});
|
|
5106
|
+
// GET /trend/:suiteName/:metricName — returns score trend for a metric
|
|
5107
|
+
router.get('/trend/:suiteName/:metricName', (_req, res) => {
|
|
5108
|
+
if (!this.evalStore) {
|
|
5109
|
+
return res.status(503).json({ error: 'Evaluation system not initialized' });
|
|
5110
|
+
}
|
|
5111
|
+
try {
|
|
5112
|
+
const trend = this.evalStore.getTrend(_req.params.suiteName, _req.params.metricName);
|
|
5113
|
+
res.json({ trend });
|
|
5114
|
+
}
|
|
5115
|
+
catch (err) {
|
|
5116
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5117
|
+
}
|
|
5118
|
+
});
|
|
5119
|
+
// POST /run — run evaluation suite
|
|
5120
|
+
router.post('/run', async (req, res) => {
|
|
5121
|
+
if (!this.evalRunner || !this.evalStore) {
|
|
5122
|
+
return res.status(503).json({ error: 'Evaluation system not initialized' });
|
|
5123
|
+
}
|
|
5124
|
+
try {
|
|
5125
|
+
const { suiteName, cases, mode } = req.body;
|
|
5126
|
+
if (!suiteName || !cases || !Array.isArray(cases) || cases.length === 0) {
|
|
5127
|
+
return res.status(400).json({ error: 'suiteName and non-empty cases array required' });
|
|
5128
|
+
}
|
|
5129
|
+
const handler = mode === 'echo' || !mode
|
|
5130
|
+
? async (input) => input
|
|
5131
|
+
: async (input) => input;
|
|
5132
|
+
const result = await this.evalRunner.runSuite(suiteName, cases, handler);
|
|
5133
|
+
this.evalStore.record(result);
|
|
5134
|
+
res.json(result);
|
|
5135
|
+
}
|
|
5136
|
+
catch (err) {
|
|
5137
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5138
|
+
}
|
|
5139
|
+
});
|
|
5140
|
+
// POST /compare — compare latest results of two suites
|
|
5141
|
+
router.post('/compare', (_req, res) => {
|
|
5142
|
+
if (!this.evalRunner || !this.evalStore) {
|
|
5143
|
+
return res.status(503).json({ error: 'Evaluation system not initialized' });
|
|
5144
|
+
}
|
|
5145
|
+
try {
|
|
5146
|
+
const { suiteNameA, suiteNameB } = _req.body;
|
|
5147
|
+
if (!suiteNameA || !suiteNameB) {
|
|
5148
|
+
return res.status(400).json({ error: 'suiteNameA and suiteNameB required' });
|
|
5149
|
+
}
|
|
5150
|
+
const latestA = this.evalStore.getLatest(suiteNameA);
|
|
5151
|
+
const latestB = this.evalStore.getLatest(suiteNameB);
|
|
5152
|
+
if (!latestA) {
|
|
5153
|
+
return res.status(404).json({ error: `No results found for suite: ${suiteNameA}` });
|
|
5154
|
+
}
|
|
5155
|
+
if (!latestB) {
|
|
5156
|
+
return res.status(404).json({ error: `No results found for suite: ${suiteNameB}` });
|
|
5157
|
+
}
|
|
5158
|
+
const comparison = this.evalRunner.compareSuites(latestA, latestB);
|
|
5159
|
+
res.json(comparison);
|
|
5160
|
+
}
|
|
5161
|
+
catch (err) {
|
|
5162
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5163
|
+
}
|
|
5164
|
+
});
|
|
5165
|
+
return router;
|
|
5166
|
+
}
|
|
5167
|
+
createCodeRouter() {
|
|
5168
|
+
const router = Router();
|
|
5169
|
+
// POST /execute — one-shot code execution (no session required)
|
|
5170
|
+
router.post('/execute', async (req, res) => {
|
|
5171
|
+
try {
|
|
5172
|
+
const { language, code, timeout } = req.body;
|
|
5173
|
+
if (!language || typeof language !== 'string') {
|
|
5174
|
+
return res.status(400).json({ error: 'language required' });
|
|
5175
|
+
}
|
|
5176
|
+
if (!code || typeof code !== 'string') {
|
|
5177
|
+
return res.status(400).json({ error: 'code required' });
|
|
5178
|
+
}
|
|
5179
|
+
const validLanguages = ['javascript', 'typescript', 'python', 'shell'];
|
|
5180
|
+
if (!validLanguages.includes(language)) {
|
|
5181
|
+
return res.status(400).json({ error: `Invalid language. Allowed: ${validLanguages.join(', ')}` });
|
|
5182
|
+
}
|
|
5183
|
+
const result = await this.codeExecutor.execute({
|
|
5184
|
+
language: language,
|
|
5185
|
+
code,
|
|
5186
|
+
timeoutMs: timeout,
|
|
5187
|
+
});
|
|
5188
|
+
res.json(result);
|
|
5189
|
+
}
|
|
5190
|
+
catch (err) {
|
|
5191
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5192
|
+
}
|
|
5193
|
+
});
|
|
5194
|
+
// GET /sessions — list active sessions
|
|
5195
|
+
router.get('/sessions', (_req, res) => {
|
|
5196
|
+
const sessions = this.codeSessionManager.listSessions().map((s) => ({
|
|
5197
|
+
id: s.id,
|
|
5198
|
+
language: s.language,
|
|
5199
|
+
createdAt: s.createdAt,
|
|
5200
|
+
lastActivity: s.lastActivity,
|
|
5201
|
+
historyLength: s.history.length,
|
|
5202
|
+
}));
|
|
5203
|
+
res.json({ sessions });
|
|
5204
|
+
});
|
|
5205
|
+
// POST /sessions — create a new REPL session
|
|
5206
|
+
router.post('/sessions', (req, res) => {
|
|
5207
|
+
try {
|
|
5208
|
+
const { language } = req.body;
|
|
5209
|
+
if (!language || typeof language !== 'string') {
|
|
5210
|
+
return res.status(400).json({ error: 'language required' });
|
|
5211
|
+
}
|
|
5212
|
+
const validLanguages = ['javascript', 'typescript', 'python', 'shell'];
|
|
5213
|
+
if (!validLanguages.includes(language)) {
|
|
5214
|
+
return res.status(400).json({ error: `Invalid language. Allowed: ${validLanguages.join(', ')}` });
|
|
5215
|
+
}
|
|
5216
|
+
const session = this.codeSessionManager.createSession(language);
|
|
5217
|
+
res.status(201).json({
|
|
5218
|
+
id: session.id,
|
|
5219
|
+
language: session.language,
|
|
5220
|
+
createdAt: session.createdAt,
|
|
5221
|
+
});
|
|
5222
|
+
}
|
|
5223
|
+
catch (err) {
|
|
5224
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5225
|
+
const status = message.includes('Maximum number of sessions') ? 409 : 500;
|
|
5226
|
+
res.status(status).json({ error: message });
|
|
5227
|
+
}
|
|
5228
|
+
});
|
|
5229
|
+
// GET /sessions/:id — get session details
|
|
5230
|
+
router.get('/sessions/:id', (req, res) => {
|
|
5231
|
+
const session = this.codeSessionManager.getSession(req.params.id);
|
|
5232
|
+
if (!session) {
|
|
5233
|
+
return res.status(404).json({ error: 'session not found' });
|
|
5234
|
+
}
|
|
5235
|
+
res.json({
|
|
5236
|
+
id: session.id,
|
|
5237
|
+
language: session.language,
|
|
5238
|
+
createdAt: session.createdAt,
|
|
5239
|
+
lastActivity: session.lastActivity,
|
|
5240
|
+
historyLength: session.history.length,
|
|
5241
|
+
history: session.history,
|
|
5242
|
+
});
|
|
5243
|
+
});
|
|
5244
|
+
// POST /sessions/:id/execute — execute code in a session
|
|
5245
|
+
router.post('/sessions/:id/execute', async (req, res) => {
|
|
5246
|
+
try {
|
|
5247
|
+
const { code } = req.body;
|
|
5248
|
+
if (!code || typeof code !== 'string') {
|
|
5249
|
+
return res.status(400).json({ error: 'code required' });
|
|
5250
|
+
}
|
|
5251
|
+
const result = await this.codeSessionManager.execute(req.params.id, code);
|
|
5252
|
+
res.json(result);
|
|
5253
|
+
}
|
|
5254
|
+
catch (err) {
|
|
5255
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5256
|
+
if (message.includes('not found')) {
|
|
5257
|
+
return res.status(404).json({ error: message });
|
|
5258
|
+
}
|
|
5259
|
+
res.status(500).json({ error: message });
|
|
5260
|
+
}
|
|
5261
|
+
});
|
|
5262
|
+
// DELETE /sessions/:id — destroy a session
|
|
5263
|
+
router.delete('/sessions/:id', (req, res) => {
|
|
5264
|
+
const session = this.codeSessionManager.getSession(req.params.id);
|
|
5265
|
+
if (!session) {
|
|
5266
|
+
return res.status(404).json({ error: 'session not found' });
|
|
5267
|
+
}
|
|
5268
|
+
this.codeSessionManager.destroySession(req.params.id);
|
|
5269
|
+
res.json({ deleted: true });
|
|
5270
|
+
});
|
|
5271
|
+
return router;
|
|
5272
|
+
}
|
|
5273
|
+
createBackupRouter() {
|
|
5274
|
+
const router = Router();
|
|
5275
|
+
let nextId = 1;
|
|
5276
|
+
// POST /create — create a new backup
|
|
5277
|
+
router.post('/create', async (req, res) => {
|
|
5278
|
+
if (!this.backupManager) {
|
|
5279
|
+
return res.status(503).json({ error: 'Backup system not initialized' });
|
|
5280
|
+
}
|
|
5281
|
+
try {
|
|
5282
|
+
const { categories } = req.body;
|
|
5283
|
+
const result = await this.backupManager.createBackup(categories);
|
|
5284
|
+
if (result.status === 'failed') {
|
|
5285
|
+
return res.status(500).json({ error: result.error ?? 'Backup failed' });
|
|
5286
|
+
}
|
|
5287
|
+
const id = `backup-${nextId++}`;
|
|
5288
|
+
this.backupStore.set(id, result);
|
|
5289
|
+
res.status(201).json({ id, ...result });
|
|
5290
|
+
}
|
|
5291
|
+
catch (err) {
|
|
5292
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5293
|
+
}
|
|
5294
|
+
});
|
|
5295
|
+
// GET /list — list all stored backups
|
|
5296
|
+
router.get('/list', (_req, res) => {
|
|
5297
|
+
const backups = [...this.backupStore.entries()].map(([id, b]) => ({
|
|
5298
|
+
id,
|
|
5299
|
+
status: b.status,
|
|
5300
|
+
manifest: b.manifest,
|
|
5301
|
+
}));
|
|
5302
|
+
res.json({ backups });
|
|
5303
|
+
});
|
|
5304
|
+
// POST /restore — restore from a backup
|
|
5305
|
+
router.post('/restore', async (req, res) => {
|
|
5306
|
+
if (!this.backupManager) {
|
|
5307
|
+
return res.status(503).json({ error: 'Backup system not initialized' });
|
|
5308
|
+
}
|
|
5309
|
+
try {
|
|
5310
|
+
const { backupId, categories } = req.body;
|
|
5311
|
+
if (!backupId || typeof backupId !== 'string') {
|
|
5312
|
+
return res.status(400).json({ error: 'backupId required' });
|
|
5313
|
+
}
|
|
5314
|
+
const backup = this.backupStore.get(backupId);
|
|
5315
|
+
if (!backup) {
|
|
5316
|
+
return res.status(404).json({ error: 'Backup not found' });
|
|
5317
|
+
}
|
|
5318
|
+
const result = await this.backupManager.restore(backup, categories);
|
|
5319
|
+
if (result.status === 'failed') {
|
|
5320
|
+
return res.status(500).json({ error: result.error ?? 'Restore failed' });
|
|
5321
|
+
}
|
|
5322
|
+
res.json(result);
|
|
5323
|
+
}
|
|
5324
|
+
catch (err) {
|
|
5325
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5326
|
+
}
|
|
5327
|
+
});
|
|
5328
|
+
// DELETE /:id — delete a stored backup
|
|
5329
|
+
router.delete('/:id', (req, res) => {
|
|
5330
|
+
const { id } = req.params;
|
|
5331
|
+
if (!this.backupStore.has(id)) {
|
|
5332
|
+
return res.status(404).json({ error: 'Backup not found' });
|
|
5333
|
+
}
|
|
5334
|
+
this.backupStore.delete(id);
|
|
5335
|
+
res.json({ deleted: true });
|
|
5336
|
+
});
|
|
5337
|
+
return router;
|
|
5338
|
+
}
|
|
5339
|
+
createKnowledgeRouter() {
|
|
5340
|
+
const router = Router();
|
|
5341
|
+
router.post('/entities', (req, res) => {
|
|
5342
|
+
const { name, type, properties, aliases } = req.body;
|
|
5343
|
+
if (!name || typeof name !== 'string')
|
|
5344
|
+
return res.status(400).json({ error: 'name required' });
|
|
5345
|
+
if (!type || typeof type !== 'string')
|
|
5346
|
+
return res.status(400).json({ error: 'type required' });
|
|
5347
|
+
try {
|
|
5348
|
+
const node = this.knowledgeGraph.addNode({ name, type: type, aliases: aliases ?? [], properties: properties ?? {}, confidence: 1.0 });
|
|
5349
|
+
res.status(201).json(node);
|
|
5350
|
+
}
|
|
5351
|
+
catch (err) {
|
|
5352
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5353
|
+
}
|
|
5354
|
+
});
|
|
5355
|
+
router.get('/entities', (req, res) => {
|
|
5356
|
+
const query = req.query.query;
|
|
5357
|
+
if (query) {
|
|
5358
|
+
const extracted = this.entityLinker.extractEntities(query);
|
|
5359
|
+
const nodes = extracted.map((e) => this.knowledgeGraph.getNode(e.name)).filter(Boolean);
|
|
5360
|
+
return res.json({ entities: nodes });
|
|
5361
|
+
}
|
|
5362
|
+
return res.json({ entities: [], stats: this.knowledgeGraph.stats() });
|
|
5363
|
+
});
|
|
5364
|
+
router.get('/entities/:id', (req, res) => {
|
|
5365
|
+
const node = this.knowledgeGraph.getNode(req.params.id);
|
|
5366
|
+
if (!node)
|
|
5367
|
+
return res.status(404).json({ error: 'entity not found' });
|
|
5368
|
+
res.json(node);
|
|
5369
|
+
});
|
|
5370
|
+
router.delete('/entities/:id', (req, res) => {
|
|
5371
|
+
const node = this.knowledgeGraph.getNode(req.params.id);
|
|
5372
|
+
if (!node)
|
|
5373
|
+
return res.status(404).json({ error: 'entity not found' });
|
|
5374
|
+
this.knowledgeGraph.removeNode(node.id);
|
|
5375
|
+
res.json({ deleted: true });
|
|
5376
|
+
});
|
|
5377
|
+
router.post('/relations', (req, res) => {
|
|
5378
|
+
const { from, to, type, properties, weight, label, evidence } = req.body;
|
|
5379
|
+
if (!from || typeof from !== 'string')
|
|
5380
|
+
return res.status(400).json({ error: 'from required' });
|
|
5381
|
+
if (!to || typeof to !== 'string')
|
|
5382
|
+
return res.status(400).json({ error: 'to required' });
|
|
5383
|
+
if (!type || typeof type !== 'string')
|
|
5384
|
+
return res.status(400).json({ error: 'type required' });
|
|
5385
|
+
try {
|
|
5386
|
+
const edge = this.knowledgeGraph.addEdge(from, to, type, { weight, label, evidence, properties });
|
|
5387
|
+
res.status(201).json(edge);
|
|
5388
|
+
}
|
|
5389
|
+
catch (err) {
|
|
5390
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5391
|
+
if (message.includes('not found'))
|
|
5392
|
+
return res.status(404).json({ error: message });
|
|
5393
|
+
res.status(500).json({ error: message });
|
|
5394
|
+
}
|
|
5395
|
+
});
|
|
5396
|
+
router.get('/relations', (req, res) => {
|
|
5397
|
+
const nodeId = req.query.nodeId;
|
|
5398
|
+
const direction = req.query.direction ?? 'both';
|
|
5399
|
+
if (!nodeId)
|
|
5400
|
+
return res.status(400).json({ error: 'nodeId query parameter required' });
|
|
5401
|
+
const node = this.knowledgeGraph.getNode(nodeId);
|
|
5402
|
+
if (!node)
|
|
5403
|
+
return res.status(404).json({ error: 'node not found' });
|
|
5404
|
+
res.json({ relations: this.knowledgeGraph.getEdges(node.id, direction) });
|
|
5405
|
+
});
|
|
5406
|
+
router.post('/query', (req, res) => {
|
|
5407
|
+
const { startNode, relation, targetType, maxDepth, minConfidence } = req.body;
|
|
5408
|
+
try {
|
|
5409
|
+
res.json(this.knowledgeGraph.query({ startNode, relation, targetType, maxDepth, minConfidence }));
|
|
5410
|
+
}
|
|
5411
|
+
catch (err) {
|
|
5412
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5413
|
+
}
|
|
5414
|
+
});
|
|
5415
|
+
router.post('/extract', (req, res) => {
|
|
5416
|
+
const { text } = req.body;
|
|
5417
|
+
if (!text || typeof text !== 'string')
|
|
5418
|
+
return res.status(400).json({ error: 'text required' });
|
|
5419
|
+
try {
|
|
5420
|
+
const result = this.entityLinker.linkToGraph(text, this.knowledgeGraph);
|
|
5421
|
+
res.json({ newNodes: result.newNodes, newEdges: result.newEdges });
|
|
5422
|
+
}
|
|
5423
|
+
catch (err) {
|
|
5424
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5425
|
+
}
|
|
5426
|
+
});
|
|
5427
|
+
router.get('/stats', (_req, res) => {
|
|
5428
|
+
res.json(this.knowledgeGraph.stats());
|
|
5429
|
+
});
|
|
5430
|
+
return router;
|
|
5431
|
+
}
|
|
5432
|
+
createAutomationRouter() {
|
|
5433
|
+
const router = Router();
|
|
5434
|
+
router.post('/parse', (req, res) => {
|
|
5435
|
+
const { input } = req.body;
|
|
5436
|
+
if (!input || typeof input !== 'string') {
|
|
5437
|
+
return res.status(400).json({ error: 'input required' });
|
|
5438
|
+
}
|
|
5439
|
+
const spec = this.nlIntentParser.parse(input);
|
|
5440
|
+
res.json({ spec });
|
|
5441
|
+
});
|
|
5442
|
+
router.post('/build', (req, res) => {
|
|
5443
|
+
const { spec } = req.body;
|
|
5444
|
+
if (!spec || typeof spec !== 'object') {
|
|
5445
|
+
return res.status(400).json({ error: 'spec required' });
|
|
5446
|
+
}
|
|
5447
|
+
try {
|
|
5448
|
+
const behavior = this.automationBuilder.build(spec);
|
|
5449
|
+
res.json({ behavior });
|
|
5450
|
+
}
|
|
5451
|
+
catch (err) {
|
|
5452
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5453
|
+
}
|
|
5454
|
+
});
|
|
5455
|
+
router.post('/validate', (req, res) => {
|
|
5456
|
+
const { spec } = req.body;
|
|
5457
|
+
if (!spec || typeof spec !== 'object') {
|
|
5458
|
+
return res.status(400).json({ error: 'spec required' });
|
|
5459
|
+
}
|
|
5460
|
+
const result = this.automationBuilder.validate(spec, [], []);
|
|
5461
|
+
res.json({ result });
|
|
5462
|
+
});
|
|
5463
|
+
return router;
|
|
5464
|
+
}
|
|
5465
|
+
createBranchRouter() {
|
|
5466
|
+
const router = Router();
|
|
5467
|
+
const getOrCreateManager = (conversationId) => {
|
|
5468
|
+
let mgr = this.branchManagers.get(conversationId);
|
|
5469
|
+
if (!mgr) {
|
|
5470
|
+
mgr = new BranchManager(conversationId);
|
|
5471
|
+
this.branchManagers.set(conversationId, mgr);
|
|
5472
|
+
}
|
|
5473
|
+
return mgr;
|
|
5474
|
+
};
|
|
5475
|
+
router.get('/:conversationId', (req, res) => {
|
|
5476
|
+
const mgr = getOrCreateManager(req.params.conversationId);
|
|
5477
|
+
res.json({ branches: mgr.listBranches(), active: mgr.getActiveBranch() });
|
|
5478
|
+
});
|
|
5479
|
+
router.post('/:conversationId/fork', (req, res) => {
|
|
5480
|
+
const mgr = getOrCreateManager(req.params.conversationId);
|
|
5481
|
+
const { messageId, label } = req.body;
|
|
5482
|
+
try {
|
|
5483
|
+
const branch = mgr.fork(messageId, label);
|
|
5484
|
+
res.status(201).json(branch);
|
|
5485
|
+
}
|
|
5486
|
+
catch (err) {
|
|
5487
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5488
|
+
}
|
|
5489
|
+
});
|
|
5490
|
+
router.post('/:conversationId/switch', (req, res) => {
|
|
5491
|
+
const mgr = getOrCreateManager(req.params.conversationId);
|
|
5492
|
+
const { branchId } = req.body;
|
|
5493
|
+
if (!branchId || typeof branchId !== 'string') {
|
|
5494
|
+
return res.status(400).json({ error: 'branchId required' });
|
|
5495
|
+
}
|
|
5496
|
+
try {
|
|
5497
|
+
const branch = mgr.switchBranch(branchId);
|
|
5498
|
+
res.json(branch);
|
|
5499
|
+
}
|
|
5500
|
+
catch (err) {
|
|
5501
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5502
|
+
}
|
|
5503
|
+
});
|
|
5504
|
+
router.get('/:conversationId/tree', (req, res) => {
|
|
5505
|
+
const mgr = getOrCreateManager(req.params.conversationId);
|
|
5506
|
+
res.json({ tree: mgr.getTree() });
|
|
5507
|
+
});
|
|
5508
|
+
router.post('/:conversationId/merge', (req, res) => {
|
|
5509
|
+
const mgr = getOrCreateManager(req.params.conversationId);
|
|
5510
|
+
const { sourceBranchId, targetBranchId } = req.body;
|
|
5511
|
+
if (!sourceBranchId || typeof sourceBranchId !== 'string') {
|
|
5512
|
+
return res.status(400).json({ error: 'sourceBranchId required' });
|
|
5513
|
+
}
|
|
5514
|
+
if (!targetBranchId || typeof targetBranchId !== 'string') {
|
|
5515
|
+
return res.status(400).json({ error: 'targetBranchId required' });
|
|
5516
|
+
}
|
|
5517
|
+
try {
|
|
5518
|
+
mgr.mergeBranch(sourceBranchId, targetBranchId);
|
|
5519
|
+
res.json({ merged: true });
|
|
5520
|
+
}
|
|
5521
|
+
catch (err) {
|
|
5522
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5523
|
+
}
|
|
5524
|
+
});
|
|
5525
|
+
router.delete('/:conversationId/:branchId', (req, res) => {
|
|
5526
|
+
const mgr = getOrCreateManager(req.params.conversationId);
|
|
5527
|
+
try {
|
|
5528
|
+
mgr.deleteBranch(req.params.branchId);
|
|
5529
|
+
res.json({ deleted: true });
|
|
5530
|
+
}
|
|
5531
|
+
catch (err) {
|
|
5532
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5533
|
+
}
|
|
5534
|
+
});
|
|
5535
|
+
return router;
|
|
5536
|
+
}
|
|
5537
|
+
createApprovalQueueRouter() {
|
|
5538
|
+
const router = Router();
|
|
5539
|
+
// GET / — list all approval requests
|
|
5540
|
+
router.get('/', (_req, res) => {
|
|
5541
|
+
if (!this.approvalQueue) {
|
|
5542
|
+
return res.status(503).json({ error: 'Approval queue not initialized' });
|
|
5543
|
+
}
|
|
5544
|
+
res.json(this.approvalQueue.listAll());
|
|
5545
|
+
});
|
|
5546
|
+
// GET /pending — list pending approval requests
|
|
5547
|
+
router.get('/pending', (_req, res) => {
|
|
5548
|
+
if (!this.approvalQueue) {
|
|
5549
|
+
return res.status(503).json({ error: 'Approval queue not initialized' });
|
|
5550
|
+
}
|
|
5551
|
+
res.json(this.approvalQueue.listPending());
|
|
5552
|
+
});
|
|
5553
|
+
// POST /expire — expire stale requests
|
|
5554
|
+
router.post('/expire', (_req, res) => {
|
|
5555
|
+
if (!this.approvalQueue) {
|
|
5556
|
+
return res.status(503).json({ error: 'Approval queue not initialized' });
|
|
5557
|
+
}
|
|
5558
|
+
const expired = this.approvalQueue.expireStale();
|
|
5559
|
+
res.json({ expired });
|
|
5560
|
+
});
|
|
5561
|
+
// GET /:id — get a specific approval request
|
|
5562
|
+
router.get('/:id', (req, res) => {
|
|
5563
|
+
if (!this.approvalQueue) {
|
|
5564
|
+
return res.status(503).json({ error: 'Approval queue not initialized' });
|
|
5565
|
+
}
|
|
5566
|
+
const request = this.approvalQueue.get(req.params.id);
|
|
5567
|
+
if (!request) {
|
|
5568
|
+
return res.status(404).json({ error: 'Approval request not found' });
|
|
5569
|
+
}
|
|
5570
|
+
res.json(request);
|
|
5571
|
+
});
|
|
5572
|
+
// POST /:id/approve — approve a request
|
|
5573
|
+
router.post('/:id/approve', (req, res) => {
|
|
5574
|
+
if (!this.approvalQueue) {
|
|
5575
|
+
return res.status(503).json({ error: 'Approval queue not initialized' });
|
|
5576
|
+
}
|
|
5577
|
+
try {
|
|
5578
|
+
const { decidedBy } = req.body;
|
|
5579
|
+
const result = this.approvalQueue.approve(req.params.id, decidedBy);
|
|
5580
|
+
res.json(result);
|
|
5581
|
+
}
|
|
5582
|
+
catch (err) {
|
|
5583
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5584
|
+
if (message.includes('not found')) {
|
|
5585
|
+
return res.status(404).json({ error: message });
|
|
5586
|
+
}
|
|
5587
|
+
if (message.includes('already')) {
|
|
5588
|
+
return res.status(409).json({ error: message });
|
|
5589
|
+
}
|
|
5590
|
+
res.status(500).json({ error: message });
|
|
5591
|
+
}
|
|
5592
|
+
});
|
|
5593
|
+
// POST /:id/deny — deny a request
|
|
5594
|
+
router.post('/:id/deny', (req, res) => {
|
|
5595
|
+
if (!this.approvalQueue) {
|
|
5596
|
+
return res.status(503).json({ error: 'Approval queue not initialized' });
|
|
5597
|
+
}
|
|
5598
|
+
try {
|
|
5599
|
+
const { decidedBy, reason } = req.body;
|
|
5600
|
+
const result = this.approvalQueue.deny(req.params.id, reason, decidedBy);
|
|
5601
|
+
res.json(result);
|
|
5602
|
+
}
|
|
5603
|
+
catch (err) {
|
|
5604
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5605
|
+
if (message.includes('not found')) {
|
|
5606
|
+
return res.status(404).json({ error: message });
|
|
5607
|
+
}
|
|
5608
|
+
if (message.includes('already')) {
|
|
5609
|
+
return res.status(409).json({ error: message });
|
|
5610
|
+
}
|
|
5611
|
+
res.status(500).json({ error: message });
|
|
5612
|
+
}
|
|
5613
|
+
});
|
|
5614
|
+
return router;
|
|
5615
|
+
}
|
|
5616
|
+
createVectorRouter() {
|
|
5617
|
+
const router = Router();
|
|
5618
|
+
// GET /stats — get store statistics
|
|
5619
|
+
router.get('/stats', (_req, res) => {
|
|
5620
|
+
if (!this.vectorStore) {
|
|
5621
|
+
return res.status(503).json({ error: 'Vector store not initialized' });
|
|
5622
|
+
}
|
|
5623
|
+
res.json({ size: this.vectorStore.size() });
|
|
5624
|
+
});
|
|
5625
|
+
// POST / — add a vector entry
|
|
5626
|
+
router.post('/', (req, res) => {
|
|
5627
|
+
if (!this.vectorStore) {
|
|
5628
|
+
return res.status(503).json({ error: 'Vector store not initialized' });
|
|
5629
|
+
}
|
|
5630
|
+
try {
|
|
5631
|
+
const { id, vector, content, metadata } = req.body;
|
|
5632
|
+
if (!id || typeof id !== 'string') {
|
|
5633
|
+
return res.status(400).json({ error: 'id required' });
|
|
5634
|
+
}
|
|
5635
|
+
if (!vector || !Array.isArray(vector)) {
|
|
5636
|
+
return res.status(400).json({ error: 'vector required' });
|
|
5637
|
+
}
|
|
5638
|
+
const entry = this.vectorStore.add(id, vector, content ?? '', metadata);
|
|
5639
|
+
res.status(201).json(entry);
|
|
5640
|
+
}
|
|
5641
|
+
catch (err) {
|
|
5642
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5643
|
+
}
|
|
5644
|
+
});
|
|
5645
|
+
// POST /search — search vectors
|
|
5646
|
+
router.post('/search', (req, res) => {
|
|
5647
|
+
if (!this.vectorStore) {
|
|
5648
|
+
return res.status(503).json({ error: 'Vector store not initialized' });
|
|
5649
|
+
}
|
|
5650
|
+
try {
|
|
5651
|
+
const { vector, limit, minScore } = req.body;
|
|
5652
|
+
if (!vector || !Array.isArray(vector)) {
|
|
5653
|
+
return res.status(400).json({ error: 'vector required' });
|
|
5654
|
+
}
|
|
5655
|
+
const results = this.vectorStore.search(vector, limit, minScore);
|
|
5656
|
+
res.json(results);
|
|
5657
|
+
}
|
|
5658
|
+
catch (err) {
|
|
5659
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5660
|
+
}
|
|
5661
|
+
});
|
|
5662
|
+
// GET /:id — get a vector entry by ID
|
|
5663
|
+
router.get('/:id', (req, res) => {
|
|
5664
|
+
if (!this.vectorStore) {
|
|
5665
|
+
return res.status(503).json({ error: 'Vector store not initialized' });
|
|
5666
|
+
}
|
|
5667
|
+
const entry = this.vectorStore.get(req.params.id);
|
|
5668
|
+
if (!entry) {
|
|
5669
|
+
return res.status(404).json({ error: 'Vector entry not found' });
|
|
5670
|
+
}
|
|
5671
|
+
res.json(entry);
|
|
5672
|
+
});
|
|
5673
|
+
// DELETE /:id — remove a vector entry
|
|
5674
|
+
router.delete('/:id', (req, res) => {
|
|
5675
|
+
if (!this.vectorStore) {
|
|
5676
|
+
return res.status(503).json({ error: 'Vector store not initialized' });
|
|
5677
|
+
}
|
|
5678
|
+
const removed = this.vectorStore.remove(req.params.id);
|
|
5679
|
+
if (!removed) {
|
|
5680
|
+
return res.status(404).json({ error: 'Vector entry not found' });
|
|
5681
|
+
}
|
|
5682
|
+
res.json({ deleted: true });
|
|
5683
|
+
});
|
|
5684
|
+
return router;
|
|
5685
|
+
}
|
|
5686
|
+
createReactRouter() {
|
|
5687
|
+
const router = Router();
|
|
5688
|
+
// POST /run — start a new ReAct loop
|
|
5689
|
+
router.post('/run', (req, res) => {
|
|
5690
|
+
const { goal, maxSteps } = req.body;
|
|
5691
|
+
if (!goal || typeof goal !== 'string') {
|
|
5692
|
+
return res.status(400).json({ error: 'goal is required' });
|
|
5693
|
+
}
|
|
5694
|
+
const id = crypto.randomUUID();
|
|
5695
|
+
const config = {};
|
|
5696
|
+
if (maxSteps) {
|
|
5697
|
+
config.maxSteps = maxSteps;
|
|
5698
|
+
}
|
|
5699
|
+
const callbacks = {
|
|
5700
|
+
think: async (g, history) => {
|
|
5701
|
+
// Simple stub: produce an answer after gathering context
|
|
5702
|
+
if (history.length > 0) {
|
|
5703
|
+
return { thought: `Analyzed goal: ${g}`, answer: `Completed goal: ${g}` };
|
|
5704
|
+
}
|
|
5705
|
+
return { thought: `Planning approach for: ${g}` };
|
|
5706
|
+
},
|
|
5707
|
+
executeTool: async (toolName, params) => {
|
|
5708
|
+
try {
|
|
5709
|
+
const result = await toolExecutor.execute(toolName, params, {
|
|
5710
|
+
sessionId: id,
|
|
5711
|
+
userId: 'react-loop',
|
|
5712
|
+
});
|
|
5713
|
+
return typeof result === 'string' ? result : JSON.stringify(result);
|
|
5714
|
+
}
|
|
5715
|
+
catch (err) {
|
|
5716
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
5717
|
+
}
|
|
5718
|
+
},
|
|
5719
|
+
};
|
|
5720
|
+
const loop = new ReActLoop(callbacks, config);
|
|
5721
|
+
this.reactLoops.set(id, loop);
|
|
5722
|
+
// Run async — do not await
|
|
5723
|
+
loop.run(goal).then((result) => {
|
|
5724
|
+
this.reactResults.set(id, result);
|
|
5725
|
+
}).catch(() => {
|
|
5726
|
+
// Errors captured in result
|
|
5727
|
+
});
|
|
5728
|
+
res.status(201).json({ id, status: loop.getStatus() });
|
|
5729
|
+
});
|
|
5730
|
+
// GET /:id/status — get loop status
|
|
5731
|
+
router.get('/:id/status', (req, res) => {
|
|
5732
|
+
const loop = this.reactLoops.get(req.params.id);
|
|
5733
|
+
if (!loop) {
|
|
5734
|
+
return res.status(404).json({ error: 'Loop not found' });
|
|
5735
|
+
}
|
|
5736
|
+
const result = this.reactResults.get(req.params.id);
|
|
5737
|
+
res.json({ status: loop.getStatus(), result: result ?? null });
|
|
5738
|
+
});
|
|
5739
|
+
// GET /:id/steps — get loop steps
|
|
5740
|
+
router.get('/:id/steps', (req, res) => {
|
|
5741
|
+
const loop = this.reactLoops.get(req.params.id);
|
|
5742
|
+
if (!loop) {
|
|
5743
|
+
return res.status(404).json({ error: 'Loop not found' });
|
|
5744
|
+
}
|
|
5745
|
+
res.json(loop.getSteps());
|
|
5746
|
+
});
|
|
5747
|
+
// POST /:id/pause — pause loop
|
|
5748
|
+
router.post('/:id/pause', (req, res) => {
|
|
5749
|
+
const loop = this.reactLoops.get(req.params.id);
|
|
5750
|
+
if (!loop) {
|
|
5751
|
+
return res.status(404).json({ error: 'Loop not found' });
|
|
5752
|
+
}
|
|
5753
|
+
loop.pause();
|
|
5754
|
+
res.json({ status: loop.getStatus() });
|
|
5755
|
+
});
|
|
5756
|
+
// POST /:id/resume — resume loop
|
|
5757
|
+
router.post('/:id/resume', (req, res) => {
|
|
5758
|
+
const loop = this.reactLoops.get(req.params.id);
|
|
5759
|
+
if (!loop) {
|
|
5760
|
+
return res.status(404).json({ error: 'Loop not found' });
|
|
5761
|
+
}
|
|
5762
|
+
loop.resume();
|
|
5763
|
+
res.json({ status: loop.getStatus() });
|
|
5764
|
+
});
|
|
5765
|
+
// POST /:id/abort — abort loop
|
|
5766
|
+
router.post('/:id/abort', (req, res) => {
|
|
5767
|
+
const loop = this.reactLoops.get(req.params.id);
|
|
5768
|
+
if (!loop) {
|
|
5769
|
+
return res.status(404).json({ error: 'Loop not found' });
|
|
5770
|
+
}
|
|
5771
|
+
loop.abort(req.body?.reason);
|
|
5772
|
+
res.json({ status: loop.getStatus() });
|
|
5773
|
+
});
|
|
5774
|
+
return router;
|
|
5775
|
+
}
|
|
5776
|
+
createA2ARouter() {
|
|
5777
|
+
const router = Router();
|
|
5778
|
+
// GET /card — return this agent's capability card
|
|
5779
|
+
router.get('/card', (_req, res) => {
|
|
5780
|
+
if (!this.a2aAgentCard) {
|
|
5781
|
+
return res.status(503).json({ error: 'A2A not initialized' });
|
|
5782
|
+
}
|
|
5783
|
+
res.json(this.a2aAgentCard);
|
|
5784
|
+
});
|
|
5785
|
+
// POST /tasks — send a task (create via task manager)
|
|
5786
|
+
router.post('/tasks', (req, res) => {
|
|
5787
|
+
const { targetUrl, task } = req.body;
|
|
5788
|
+
if (!task?.message) {
|
|
5789
|
+
return res.status(400).json({ error: 'task.message is required' });
|
|
5790
|
+
}
|
|
5791
|
+
const a2aTask = this.a2aTaskManager.createTask({
|
|
5792
|
+
role: 'user',
|
|
5793
|
+
parts: [{ type: 'text', text: task.message }],
|
|
5794
|
+
timestamp: Date.now(),
|
|
5795
|
+
});
|
|
5796
|
+
if (targetUrl) {
|
|
5797
|
+
// Store target URL as metadata for potential forwarding
|
|
5798
|
+
a2aTask.metadata = { targetUrl };
|
|
5799
|
+
}
|
|
5800
|
+
res.status(201).json(a2aTask);
|
|
5801
|
+
});
|
|
5802
|
+
// GET /tasks/:id — get task status
|
|
5803
|
+
router.get('/tasks/:id', (req, res) => {
|
|
5804
|
+
const task = this.a2aTaskManager.getTask(req.params.id);
|
|
5805
|
+
if (!task) {
|
|
5806
|
+
return res.status(404).json({ error: 'Task not found' });
|
|
5807
|
+
}
|
|
5808
|
+
res.json(task);
|
|
5809
|
+
});
|
|
5810
|
+
// POST /tasks/:id/cancel — cancel task
|
|
5811
|
+
router.post('/tasks/:id/cancel', (req, res) => {
|
|
5812
|
+
const task = this.a2aTaskManager.getTask(req.params.id);
|
|
5813
|
+
if (!task) {
|
|
5814
|
+
return res.status(404).json({ error: 'Task not found' });
|
|
5815
|
+
}
|
|
5816
|
+
try {
|
|
5817
|
+
this.a2aTaskManager.cancelTask(req.params.id);
|
|
5818
|
+
res.json(this.a2aTaskManager.getTask(req.params.id));
|
|
5819
|
+
}
|
|
5820
|
+
catch (err) {
|
|
5821
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5822
|
+
}
|
|
5823
|
+
});
|
|
5824
|
+
return router;
|
|
5825
|
+
}
|
|
5826
|
+
createCanvasRouter() {
|
|
5827
|
+
const router = Router();
|
|
5828
|
+
// POST /sessions — create a new canvas session
|
|
5829
|
+
router.post('/sessions', (req, res) => {
|
|
5830
|
+
const { width, height } = req.body;
|
|
5831
|
+
const session = new CanvasSession({ width, height });
|
|
5832
|
+
this.canvasSessions.set(session.id, session);
|
|
5833
|
+
res.status(201).json({ id: session.id, ...session.getSize() });
|
|
5834
|
+
});
|
|
5835
|
+
// GET /sessions/:id — get session state
|
|
5836
|
+
router.get('/sessions/:id', (req, res) => {
|
|
5837
|
+
const session = this.canvasSessions.get(req.params.id);
|
|
5838
|
+
if (!session) {
|
|
5839
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
5840
|
+
}
|
|
5841
|
+
res.json({ id: session.id, objects: session.getObjects(), size: session.getSize() });
|
|
5842
|
+
});
|
|
5843
|
+
// POST /sessions/:id/objects — add object
|
|
5844
|
+
router.post('/sessions/:id/objects', (req, res) => {
|
|
5845
|
+
const session = this.canvasSessions.get(req.params.id);
|
|
5846
|
+
if (!session) {
|
|
5847
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
5848
|
+
}
|
|
5849
|
+
try {
|
|
5850
|
+
const obj = session.addObject(req.body);
|
|
5851
|
+
res.status(201).json(obj);
|
|
5852
|
+
}
|
|
5853
|
+
catch (err) {
|
|
5854
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5855
|
+
}
|
|
5856
|
+
});
|
|
5857
|
+
// PUT /sessions/:id/objects/:objectId — update object
|
|
5858
|
+
router.put('/sessions/:id/objects/:objectId', (req, res) => {
|
|
5859
|
+
const session = this.canvasSessions.get(req.params.id);
|
|
5860
|
+
if (!session) {
|
|
5861
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
5862
|
+
}
|
|
5863
|
+
const updated = session.updateObject(req.params.objectId, req.body);
|
|
5864
|
+
if (!updated) {
|
|
5865
|
+
return res.status(404).json({ error: 'Object not found' });
|
|
5866
|
+
}
|
|
5867
|
+
res.json(updated);
|
|
5868
|
+
});
|
|
5869
|
+
// DELETE /sessions/:id/objects/:objectId — remove object
|
|
5870
|
+
router.delete('/sessions/:id/objects/:objectId', (req, res) => {
|
|
5871
|
+
const session = this.canvasSessions.get(req.params.id);
|
|
5872
|
+
if (!session) {
|
|
5873
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
5874
|
+
}
|
|
5875
|
+
const removed = session.removeObject(req.params.objectId);
|
|
5876
|
+
if (!removed) {
|
|
5877
|
+
return res.status(404).json({ error: 'Object not found' });
|
|
5878
|
+
}
|
|
5879
|
+
res.json({ deleted: true });
|
|
5880
|
+
});
|
|
5881
|
+
// DELETE /sessions/:id — delete session
|
|
5882
|
+
router.delete('/sessions/:id', (req, res) => {
|
|
5883
|
+
const existed = this.canvasSessions.delete(req.params.id);
|
|
5884
|
+
if (!existed) {
|
|
5885
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
5886
|
+
}
|
|
5887
|
+
res.json({ deleted: true });
|
|
5888
|
+
});
|
|
5889
|
+
return router;
|
|
5890
|
+
}
|
|
5891
|
+
createSandboxRouter() {
|
|
5892
|
+
const router = Router();
|
|
5893
|
+
// POST /sessions — create a sandbox session
|
|
5894
|
+
router.post('/sessions', async (req, res) => {
|
|
5895
|
+
if (!this.sandboxManager) {
|
|
5896
|
+
return res.status(503).json({ error: 'Sandbox not initialized' });
|
|
5897
|
+
}
|
|
5898
|
+
const { workspaceDir } = req.body;
|
|
5899
|
+
if (!workspaceDir || typeof workspaceDir !== 'string') {
|
|
5900
|
+
return res.status(400).json({ error: 'workspaceDir is required' });
|
|
5901
|
+
}
|
|
5902
|
+
try {
|
|
5903
|
+
const sessionId = crypto.randomUUID();
|
|
5904
|
+
const session = await this.sandboxManager.createSession(sessionId, workspaceDir);
|
|
5905
|
+
res.status(201).json(session.getInfo());
|
|
5906
|
+
}
|
|
5907
|
+
catch (err) {
|
|
5908
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5909
|
+
}
|
|
5910
|
+
});
|
|
5911
|
+
// POST /sessions/:id/run — run command in sandbox
|
|
5912
|
+
router.post('/sessions/:id/run', async (req, res) => {
|
|
5913
|
+
if (!this.sandboxManager) {
|
|
5914
|
+
return res.status(503).json({ error: 'Sandbox not initialized' });
|
|
5915
|
+
}
|
|
5916
|
+
const { command } = req.body;
|
|
5917
|
+
if (!command || !Array.isArray(command)) {
|
|
5918
|
+
return res.status(400).json({ error: 'command array is required' });
|
|
5919
|
+
}
|
|
5920
|
+
try {
|
|
5921
|
+
const result = await this.sandboxManager.runInSandbox(req.params.id, command);
|
|
5922
|
+
res.json(result);
|
|
5923
|
+
}
|
|
5924
|
+
catch (err) {
|
|
5925
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5926
|
+
if (message.includes('No sandbox session found')) {
|
|
5927
|
+
return res.status(404).json({ error: message });
|
|
5928
|
+
}
|
|
5929
|
+
res.status(500).json({ error: message });
|
|
5930
|
+
}
|
|
5931
|
+
});
|
|
5932
|
+
// GET /sessions/:id — get session info
|
|
5933
|
+
router.get('/sessions/:id', (req, res) => {
|
|
5934
|
+
if (!this.sandboxManager) {
|
|
5935
|
+
return res.status(503).json({ error: 'Sandbox not initialized' });
|
|
5936
|
+
}
|
|
5937
|
+
const session = this.sandboxManager.getSession(req.params.id);
|
|
5938
|
+
if (!session) {
|
|
5939
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
5940
|
+
}
|
|
5941
|
+
res.json(session.getInfo());
|
|
5942
|
+
});
|
|
5943
|
+
// DELETE /sessions/:id — stop and remove
|
|
5944
|
+
router.delete('/sessions/:id', async (req, res) => {
|
|
5945
|
+
if (!this.sandboxManager) {
|
|
5946
|
+
return res.status(503).json({ error: 'Sandbox not initialized' });
|
|
5947
|
+
}
|
|
5948
|
+
try {
|
|
5949
|
+
const removed = await this.sandboxManager.destroySession(req.params.id);
|
|
5950
|
+
if (!removed) {
|
|
5951
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
5952
|
+
}
|
|
5953
|
+
res.json({ deleted: true });
|
|
5954
|
+
}
|
|
5955
|
+
catch (err) {
|
|
5956
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5957
|
+
}
|
|
5958
|
+
});
|
|
5959
|
+
return router;
|
|
5960
|
+
}
|
|
2958
5961
|
}
|
|
2959
5962
|
function deepMerge(target, source) {
|
|
2960
5963
|
const result = { ...target };
|