@duckmind/deepquark-darwin-arm64 0.9.83 → 0.9.90
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/.deepquark/skills/bundled/knowledge-graph/SKILL.md +385 -0
- package/.deepquark/skills/bundled/knowledge-graph/STANDARDS.md +461 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/cli.ts +588 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/config.ts +630 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/connection-profile.ts +629 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/container.ts +756 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/mcp-client.ts +1310 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/output-formatter.ts +997 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/token-metrics.ts +335 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/transformation-log.ts +137 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/wrapper-config.ts +113 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/.env.example +129 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/compare-embeddings.ts +175 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/config-falkordb.yaml +108 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/config-neo4j.yaml +111 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/diagnose.ts +483 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-falkordb-dev.yml +146 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-falkordb.yml +151 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j-dev-local.yml +161 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j-dev.yml +161 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j.yml +169 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-production.yml +128 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-test.yml +10 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose.yml +84 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/entrypoint.sh +40 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/install.ts +2054 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose-falkordb.yml +78 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose-neo4j.yml +88 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose.yml +83 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-all-llms-mcp.ts +387 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-embedding-models.ts +201 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-embedding-providers.ts +641 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-graphiti-model.ts +217 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-correct.ts +141 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-llms-mcp.ts +386 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-models.ts +173 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-llama-extraction.ts +188 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-final.ts +240 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-live.ts +187 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-session.ts +127 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-model-combinations.ts +316 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-ollama-models.ts +228 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-openrouter-models.ts +460 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-real-life-mcp.ts +311 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-search-debug.ts +199 -0
- package/.deepquark/skills/bundled/knowledge-graph/tools/Install.md +104 -0
- package/.deepquark/skills/bundled/knowledge-graph/tools/README.md +120 -0
- package/.deepquark/skills/bundled/knowledge-graph/tools/knowledge-cli.ts +996 -0
- package/.deepquark/skills/bundled/knowledge-graph/tools/server-cli.ts +531 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/BulkImport.md +514 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/CaptureEpisode.md +242 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/ClearGraph.md +392 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/GetRecent.md +352 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/GetStatus.md +373 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/HealthReport.md +212 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/InvestigateEntity.md +142 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/OntologyManagement.md +201 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/RunMaintenance.md +302 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchByDate.md +255 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchFacts.md +382 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchKnowledge.md +374 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/StixImport.md +212 -0
- package/bin/deepquark +0 -0
- package/package.json +1 -1
- package/.deepquark/skills/bundled/ge-payroll/SKILL.md +0 -153
- package/.deepquark/skills/bundled/ge-payroll/evals/evals.json +0 -23
- package/.deepquark/skills/bundled/ge-payroll/references/pain-points-improvements.md +0 -106
- package/.deepquark/skills/bundled/ge-payroll/references/process-detail.md +0 -217
- package/.deepquark/skills/bundled/ge-payroll/references/raci-stakeholders.md +0 -85
- package/.deepquark/skills/bundled/ge-payroll/references/timeline-mandays.md +0 -64
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Formatter for token-efficient MCP responses
|
|
3
|
+
* @module output-formatter
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { logTransformationFailure, logTransformationSuccess } from './transformation-log';
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Types
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
export interface FormatOptions {
|
|
13
|
+
maxLines?: number;
|
|
14
|
+
maxLineLength?: number;
|
|
15
|
+
collectMetrics?: boolean;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
query?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FormatResult {
|
|
21
|
+
output: string;
|
|
22
|
+
usedFallback: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
metrics?: {
|
|
25
|
+
rawBytes: number;
|
|
26
|
+
compactBytes: number;
|
|
27
|
+
savingsPercent: number;
|
|
28
|
+
processingTimeMs: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_OPTIONS: FormatOptions = {
|
|
33
|
+
maxLines: 20,
|
|
34
|
+
maxLineLength: 120,
|
|
35
|
+
collectMetrics: false,
|
|
36
|
+
timeoutMs: 100,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type OperationFormatter = (data: unknown, options: FormatOptions) => string;
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Formatter Registry
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
const formatterRegistry = new Map<string, OperationFormatter>();
|
|
46
|
+
|
|
47
|
+
export function registerFormatter(operation: string, formatter: OperationFormatter): void {
|
|
48
|
+
formatterRegistry.set(operation, formatter);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Utility Functions (T007)
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convert ISO timestamp to human-readable relative time.
|
|
57
|
+
* Examples: "2h ago", "1d ago", "1mo ago", "1y ago"
|
|
58
|
+
*/
|
|
59
|
+
export function relativeTime(isoString: string): string {
|
|
60
|
+
const date = new Date(isoString);
|
|
61
|
+
const now = new Date();
|
|
62
|
+
const diffMs = now.getTime() - date.getTime();
|
|
63
|
+
|
|
64
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
65
|
+
const minutes = Math.floor(seconds / 60);
|
|
66
|
+
const hours = Math.floor(minutes / 60);
|
|
67
|
+
const days = Math.floor(hours / 24);
|
|
68
|
+
const months = Math.floor(days / 30);
|
|
69
|
+
const years = Math.floor(days / 365);
|
|
70
|
+
|
|
71
|
+
if (years > 0) return `${years}y ago`;
|
|
72
|
+
if (months > 0) return `${months}mo ago`;
|
|
73
|
+
if (days > 0) return `${days}d ago`;
|
|
74
|
+
if (hours > 0) return `${hours}h ago`;
|
|
75
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
76
|
+
return 'just now';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Truncate UUID to last 8 characters with ellipsis prefix.
|
|
81
|
+
* Example: "550e8400-e29b-41d4-a716-446655440000" -> "...55440000"
|
|
82
|
+
*/
|
|
83
|
+
export function truncateUuid(uuid: string): string {
|
|
84
|
+
if (!uuid || uuid.length < 8) return uuid || '';
|
|
85
|
+
return `...${uuid.slice(-8)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Truncate text at word boundary with ellipsis.
|
|
90
|
+
* The result including ellipsis will not exceed maxLength.
|
|
91
|
+
* Example: "This is a long text that needs truncation" (maxLength=25) -> "This is a long text..."
|
|
92
|
+
*/
|
|
93
|
+
export function truncateText(text: string, maxLength: number): string {
|
|
94
|
+
if (!text || text.length <= maxLength) return text || '';
|
|
95
|
+
|
|
96
|
+
// Account for ellipsis (3 chars) in max length
|
|
97
|
+
const effectiveMax = maxLength - 3;
|
|
98
|
+
if (effectiveMax <= 0) {
|
|
99
|
+
return `${text.slice(0, maxLength)}...`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Find last space before effective max
|
|
103
|
+
const truncated = text.slice(0, effectiveMax);
|
|
104
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
105
|
+
|
|
106
|
+
// If no space found or space is at the beginning, just cut at effectiveMax
|
|
107
|
+
if (lastSpace <= 0) {
|
|
108
|
+
return `${truncated.trim()}...`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return `${truncated.slice(0, lastSpace).trim()}...`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// MCP Response Type Guards
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
interface SearchNodesResponse {
|
|
119
|
+
nodes: Array<{
|
|
120
|
+
uuid?: string;
|
|
121
|
+
name: string;
|
|
122
|
+
labels?: string[]; // Actual MCP response uses labels array
|
|
123
|
+
entity_type?: string; // Legacy field
|
|
124
|
+
summary?: string;
|
|
125
|
+
created_at?: string;
|
|
126
|
+
group_id?: string;
|
|
127
|
+
attributes?: Record<string, unknown>; // For weighted scores, importance, etc.
|
|
128
|
+
}>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface SearchFactsResponse {
|
|
132
|
+
facts: Array<{
|
|
133
|
+
uuid?: string;
|
|
134
|
+
name: string; // Relation type (e.g., "ABOUT", "RELATES_TO")
|
|
135
|
+
fact: string; // Human-readable fact description
|
|
136
|
+
source_node_uuid?: string;
|
|
137
|
+
target_node_uuid?: string;
|
|
138
|
+
created_at?: string;
|
|
139
|
+
valid_at?: string;
|
|
140
|
+
// Legacy fields (older response format)
|
|
141
|
+
source?: { name: string };
|
|
142
|
+
target?: { name: string };
|
|
143
|
+
relation?: string;
|
|
144
|
+
confidence?: number;
|
|
145
|
+
}>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface GetEpisodesResponse {
|
|
149
|
+
episodes: Array<{
|
|
150
|
+
uuid?: string;
|
|
151
|
+
name: string;
|
|
152
|
+
content?: string;
|
|
153
|
+
created_at: string;
|
|
154
|
+
source_description?: string;
|
|
155
|
+
}>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface AddMemoryResponse {
|
|
159
|
+
message: string;
|
|
160
|
+
// Legacy fields (may not be present in newer server versions)
|
|
161
|
+
uuid?: string;
|
|
162
|
+
name?: string;
|
|
163
|
+
entities_extracted?: number;
|
|
164
|
+
facts_extracted?: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
interface GetStatusResponse {
|
|
168
|
+
status: string;
|
|
169
|
+
message: string;
|
|
170
|
+
// Legacy fields (may not be present in newer server versions)
|
|
171
|
+
entity_count?: number;
|
|
172
|
+
episode_count?: number;
|
|
173
|
+
last_updated?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
interface DeleteResponse {
|
|
177
|
+
success: boolean;
|
|
178
|
+
uuid?: string;
|
|
179
|
+
message?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface ClearGraphResponse {
|
|
183
|
+
success: boolean;
|
|
184
|
+
deleted_entities?: number;
|
|
185
|
+
deleted_episodes?: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Feature 009: Memory decay response types
|
|
189
|
+
interface HealthMetricsResponse {
|
|
190
|
+
states: Record<string, number>;
|
|
191
|
+
aggregates: {
|
|
192
|
+
total: number;
|
|
193
|
+
average_decay: number;
|
|
194
|
+
average_importance: number;
|
|
195
|
+
average_stability: number;
|
|
196
|
+
};
|
|
197
|
+
age_distribution: Record<string, number>;
|
|
198
|
+
maintenance: {
|
|
199
|
+
last_run: string | null;
|
|
200
|
+
duration_seconds: number;
|
|
201
|
+
processed: number;
|
|
202
|
+
transitions: number;
|
|
203
|
+
};
|
|
204
|
+
generated_at: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface MaintenanceResultResponse {
|
|
208
|
+
success: boolean;
|
|
209
|
+
memories_processed: number;
|
|
210
|
+
nodes_classified: {
|
|
211
|
+
found: number;
|
|
212
|
+
classified: number;
|
|
213
|
+
failed: number;
|
|
214
|
+
using_llm: boolean;
|
|
215
|
+
};
|
|
216
|
+
decay_scores_updated: number;
|
|
217
|
+
state_transitions: {
|
|
218
|
+
active_to_dormant: number;
|
|
219
|
+
dormant_to_archived: number;
|
|
220
|
+
archived_to_expired: number;
|
|
221
|
+
expired_to_soft_deleted: number;
|
|
222
|
+
};
|
|
223
|
+
soft_deleted_purged: number;
|
|
224
|
+
duration_seconds: number;
|
|
225
|
+
completed_at: string | null;
|
|
226
|
+
error: string | null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
interface ClassificationResponse {
|
|
230
|
+
importance: number;
|
|
231
|
+
stability: number;
|
|
232
|
+
is_permanent: boolean;
|
|
233
|
+
classification_source: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface RecoveryResponse {
|
|
237
|
+
message: string;
|
|
238
|
+
uuid: string;
|
|
239
|
+
name: string;
|
|
240
|
+
new_state: string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Feature 020: Investigative search response types
|
|
244
|
+
interface InvestigateEntityResponse {
|
|
245
|
+
entity: {
|
|
246
|
+
uuid: string;
|
|
247
|
+
name: string;
|
|
248
|
+
labels: string[];
|
|
249
|
+
summary?: string;
|
|
250
|
+
created_at?: string;
|
|
251
|
+
group_id?: string;
|
|
252
|
+
};
|
|
253
|
+
connections: Array<{
|
|
254
|
+
relationship: string;
|
|
255
|
+
direction: string;
|
|
256
|
+
hop_distance: number;
|
|
257
|
+
target_entity: {
|
|
258
|
+
uuid: string;
|
|
259
|
+
name: string;
|
|
260
|
+
labels: string[];
|
|
261
|
+
summary?: string;
|
|
262
|
+
created_at?: string;
|
|
263
|
+
group_id?: string;
|
|
264
|
+
};
|
|
265
|
+
fact?: string;
|
|
266
|
+
confidence?: number;
|
|
267
|
+
}>;
|
|
268
|
+
metadata: {
|
|
269
|
+
depth_explored: number;
|
|
270
|
+
total_connections_explored: number;
|
|
271
|
+
connections_returned: number;
|
|
272
|
+
cycles_detected: number;
|
|
273
|
+
cycles_pruned: number;
|
|
274
|
+
entities_skipped: number;
|
|
275
|
+
query_duration_ms: number;
|
|
276
|
+
max_connections_exceeded?: boolean;
|
|
277
|
+
};
|
|
278
|
+
warning?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isSearchNodesResponse(data: unknown): data is SearchNodesResponse {
|
|
282
|
+
return (
|
|
283
|
+
typeof data === 'object' &&
|
|
284
|
+
data !== null &&
|
|
285
|
+
'nodes' in data &&
|
|
286
|
+
Array.isArray((data as SearchNodesResponse).nodes)
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function isSearchFactsResponse(data: unknown): data is SearchFactsResponse {
|
|
291
|
+
if (typeof data !== 'object' || data === null) return false;
|
|
292
|
+
if (!('facts' in data)) return false;
|
|
293
|
+
const facts = (data as SearchFactsResponse).facts;
|
|
294
|
+
if (!Array.isArray(facts)) return false;
|
|
295
|
+
// Validate at least first fact has expected fields (name+fact or source+target+relation)
|
|
296
|
+
if (facts.length > 0) {
|
|
297
|
+
const first = facts[0];
|
|
298
|
+
const hasNewFormat = 'name' in first && 'fact' in first;
|
|
299
|
+
const hasLegacyFormat = 'source' in first && 'target' in first && 'relation' in first;
|
|
300
|
+
return hasNewFormat || hasLegacyFormat;
|
|
301
|
+
}
|
|
302
|
+
return true; // Empty array is valid
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function isGetEpisodesResponse(data: unknown): data is GetEpisodesResponse {
|
|
306
|
+
return (
|
|
307
|
+
typeof data === 'object' &&
|
|
308
|
+
data !== null &&
|
|
309
|
+
'episodes' in data &&
|
|
310
|
+
Array.isArray((data as GetEpisodesResponse).episodes)
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function isAddMemoryResponse(data: unknown): data is AddMemoryResponse {
|
|
315
|
+
return (
|
|
316
|
+
typeof data === 'object' &&
|
|
317
|
+
data !== null &&
|
|
318
|
+
'message' in data &&
|
|
319
|
+
typeof (data as AddMemoryResponse).message === 'string'
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isGetStatusResponse(data: unknown): data is GetStatusResponse {
|
|
324
|
+
return typeof data === 'object' && data !== null && 'status' in data && 'message' in data;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function isDeleteResponse(data: unknown): data is DeleteResponse {
|
|
328
|
+
return (
|
|
329
|
+
typeof data === 'object' &&
|
|
330
|
+
data !== null &&
|
|
331
|
+
'success' in data &&
|
|
332
|
+
typeof (data as DeleteResponse).success === 'boolean'
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function isClearGraphResponse(data: unknown): data is ClearGraphResponse {
|
|
337
|
+
return (
|
|
338
|
+
typeof data === 'object' &&
|
|
339
|
+
data !== null &&
|
|
340
|
+
'success' in data &&
|
|
341
|
+
typeof (data as ClearGraphResponse).success === 'boolean'
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function isHealthMetricsResponse(data: unknown): data is HealthMetricsResponse {
|
|
346
|
+
return (
|
|
347
|
+
typeof data === 'object' &&
|
|
348
|
+
data !== null &&
|
|
349
|
+
'states' in data &&
|
|
350
|
+
'aggregates' in data &&
|
|
351
|
+
'age_distribution' in data
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function isMaintenanceResultResponse(data: unknown): data is MaintenanceResultResponse {
|
|
356
|
+
return (
|
|
357
|
+
typeof data === 'object' &&
|
|
358
|
+
data !== null &&
|
|
359
|
+
'success' in data &&
|
|
360
|
+
'memories_processed' in data &&
|
|
361
|
+
'state_transitions' in data
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function isClassificationResponse(data: unknown): data is ClassificationResponse {
|
|
366
|
+
return (
|
|
367
|
+
typeof data === 'object' &&
|
|
368
|
+
data !== null &&
|
|
369
|
+
'importance' in data &&
|
|
370
|
+
'stability' in data &&
|
|
371
|
+
'is_permanent' in data
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function isRecoveryResponse(data: unknown): data is RecoveryResponse {
|
|
376
|
+
return (
|
|
377
|
+
typeof data === 'object' &&
|
|
378
|
+
data !== null &&
|
|
379
|
+
'message' in data &&
|
|
380
|
+
'uuid' in data &&
|
|
381
|
+
'name' in data &&
|
|
382
|
+
'new_state' in data
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function isInvestigateEntityResponse(data: unknown): data is InvestigateEntityResponse {
|
|
387
|
+
if (typeof data !== 'object' || data === null) return false;
|
|
388
|
+
const d = data as Record<string, unknown>;
|
|
389
|
+
return (
|
|
390
|
+
'entity' in d &&
|
|
391
|
+
typeof d.entity === 'object' &&
|
|
392
|
+
d.entity !== null &&
|
|
393
|
+
'connections' in d &&
|
|
394
|
+
Array.isArray(d.connections) &&
|
|
395
|
+
'metadata' in d &&
|
|
396
|
+
typeof d.metadata === 'object' &&
|
|
397
|
+
d.metadata !== null
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ============================================================================
|
|
402
|
+
// Individual Formatters (T014-T020)
|
|
403
|
+
// ============================================================================
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Format search_nodes / search_memory_nodes response
|
|
407
|
+
* Output: Found N entities for "query":
|
|
408
|
+
* 1. Name [Type] - Summary (truncated to 120 chars)
|
|
409
|
+
* When weighted scores present: 📊 Score: 0.XX (S:0.XX R:0.XX I:0.XX)
|
|
410
|
+
*/
|
|
411
|
+
export function formatSearchNodes(data: unknown, options: FormatOptions): string {
|
|
412
|
+
if (!isSearchNodesResponse(data)) {
|
|
413
|
+
throw new Error('Invalid data format for search_nodes');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const { nodes } = data;
|
|
417
|
+
const query = options.query || 'query';
|
|
418
|
+
const maxLines = options.maxLines ?? DEFAULT_OPTIONS.maxLines ?? 20;
|
|
419
|
+
|
|
420
|
+
if (nodes.length === 0) {
|
|
421
|
+
return `No entities found for "${query}"`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const lines: string[] = [`Found ${nodes.length} entities for "${query}":`];
|
|
425
|
+
|
|
426
|
+
// DEBUG: Log first node structure to understand what attributes are present
|
|
427
|
+
if (nodes.length > 0) {
|
|
428
|
+
const firstNode = nodes[0];
|
|
429
|
+
console.error('[DEBUG] First node keys:', Object.keys(firstNode));
|
|
430
|
+
console.error('[DEBUG] First node sample:', JSON.stringify(firstNode).substring(0, 500));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const displayNodes = nodes.slice(0, maxLines);
|
|
434
|
+
displayNodes.forEach((node, index) => {
|
|
435
|
+
const summary = truncateText(node.summary || '', 120);
|
|
436
|
+
// Use labels array (new format) or entity_type (legacy)
|
|
437
|
+
const entityType = node.labels?.[0] || node.entity_type || 'Entity';
|
|
438
|
+
|
|
439
|
+
let line = `${index + 1}. ${node.name} [${entityType}] - ${summary}`;
|
|
440
|
+
|
|
441
|
+
// Display weighted scores if present (Feature 009: Memory Decay Scoring)
|
|
442
|
+
if (node.attributes?.weighted_score !== undefined) {
|
|
443
|
+
const score = node.attributes.weighted_score as number;
|
|
444
|
+
const breakdown = node.attributes.score_breakdown as { semantic: number; recency: number; importance: number } | undefined;
|
|
445
|
+
|
|
446
|
+
if (breakdown) {
|
|
447
|
+
line += `\n 📊 Score: ${score.toFixed(2)} (S:${breakdown.semantic.toFixed(2)} R:${breakdown.recency.toFixed(2)} I:${breakdown.importance.toFixed(2)})`;
|
|
448
|
+
} else {
|
|
449
|
+
line += `\n 📊 Score: ${score.toFixed(2)}`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Show lifecycle state if present
|
|
453
|
+
if (node.attributes.lifecycle_state) {
|
|
454
|
+
const state = node.attributes.lifecycle_state as string;
|
|
455
|
+
line += ` [${state}]`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Show importance/stability if present
|
|
459
|
+
if (node.attributes.importance !== undefined || node.attributes.stability !== undefined) {
|
|
460
|
+
const imp = node.attributes.importance as number | undefined;
|
|
461
|
+
const stab = node.attributes.stability as number | undefined;
|
|
462
|
+
const parts = [];
|
|
463
|
+
if (imp !== undefined) parts.push(`Imp:${imp}`);
|
|
464
|
+
if (stab !== undefined) parts.push(`Stab:${stab}`);
|
|
465
|
+
if (parts.length > 0) line += ` (${parts.join(' ')})`;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
lines.push(line);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (nodes.length > maxLines) {
|
|
473
|
+
lines.push(`... and ${nodes.length - maxLines} more`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return lines.join('\n');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Format search_facts / search_memory_facts response
|
|
481
|
+
* Output: Found N facts for "query":
|
|
482
|
+
* 1. [RELATION] Fact description (truncated)
|
|
483
|
+
*/
|
|
484
|
+
export function formatSearchFacts(data: unknown, options: FormatOptions): string {
|
|
485
|
+
if (!isSearchFactsResponse(data)) {
|
|
486
|
+
throw new Error('Invalid data format for search_facts');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const { facts } = data;
|
|
490
|
+
const query = options.query || 'query';
|
|
491
|
+
const maxLines = options.maxLines ?? DEFAULT_OPTIONS.maxLines ?? 20;
|
|
492
|
+
|
|
493
|
+
if (facts.length === 0) {
|
|
494
|
+
return `No facts found for "${query}"`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const lines: string[] = [`Found ${facts.length} facts for "${query}":`];
|
|
498
|
+
|
|
499
|
+
const displayFacts = facts.slice(0, maxLines);
|
|
500
|
+
displayFacts.forEach((fact, index) => {
|
|
501
|
+
// New format: name is relation type, fact is description
|
|
502
|
+
if (fact.name && fact.fact) {
|
|
503
|
+
const relation = fact.name.toUpperCase();
|
|
504
|
+
// Use full fact text - no truncation to preserve complete knowledge
|
|
505
|
+
lines.push(`${index + 1}. [${relation}] ${fact.fact}`);
|
|
506
|
+
}
|
|
507
|
+
// Legacy format: source/target/relation objects
|
|
508
|
+
else if (fact.source && fact.target && fact.relation) {
|
|
509
|
+
const relation = fact.relation.toLowerCase().replace(/_/g, '-');
|
|
510
|
+
let line = `${index + 1}. ${fact.source.name} --${relation}--> ${fact.target.name}`;
|
|
511
|
+
if (fact.confidence !== undefined && fact.confidence > 0) {
|
|
512
|
+
line += ` (${(fact.confidence * 100).toFixed(0)}%)`;
|
|
513
|
+
}
|
|
514
|
+
lines.push(line);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
if (facts.length > maxLines) {
|
|
519
|
+
lines.push(`... and ${facts.length - maxLines} more`);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return lines.join('\n');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Format get_episodes response
|
|
527
|
+
* Output: Recent episodes (N):
|
|
528
|
+
* - [2h ago] Name - Content preview...
|
|
529
|
+
*/
|
|
530
|
+
export function formatGetEpisodes(data: unknown, options: FormatOptions): string {
|
|
531
|
+
if (!isGetEpisodesResponse(data)) {
|
|
532
|
+
throw new Error('Invalid data format for get_episodes');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const { episodes } = data;
|
|
536
|
+
const maxLines = options.maxLines ?? DEFAULT_OPTIONS.maxLines ?? 20;
|
|
537
|
+
|
|
538
|
+
if (episodes.length === 0) {
|
|
539
|
+
return 'No episodes found';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const lines: string[] = [`Recent episodes (${episodes.length}):`];
|
|
543
|
+
|
|
544
|
+
const displayEpisodes = episodes.slice(0, maxLines);
|
|
545
|
+
displayEpisodes.forEach((episode) => {
|
|
546
|
+
const time = relativeTime(episode.created_at);
|
|
547
|
+
// Use full content - no truncation to preserve complete knowledge
|
|
548
|
+
const content = episode.content || '';
|
|
549
|
+
lines.push(`- [${time}] ${episode.name} - ${content}`);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
if (episodes.length > maxLines) {
|
|
553
|
+
lines.push(`... and ${episodes.length - maxLines} more`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return lines.join('\n');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Format add_memory response
|
|
561
|
+
* Output: ✓ Episode queued: "Episode 'Name' queued for processing in group 'main'"
|
|
562
|
+
* Legacy: ✓ Episode added: "Name" (id: ...uuid8)
|
|
563
|
+
* Extracted: N entities, M facts
|
|
564
|
+
*/
|
|
565
|
+
export function formatAddMemory(data: unknown, _options: FormatOptions): string {
|
|
566
|
+
if (!isAddMemoryResponse(data)) {
|
|
567
|
+
throw new Error('Invalid data format for add_memory');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// New format: server returns a message string
|
|
571
|
+
const lines: string[] = [`✓ ${data.message}`];
|
|
572
|
+
|
|
573
|
+
// Legacy format: include uuid and extraction stats if available
|
|
574
|
+
if (data.uuid) {
|
|
575
|
+
const uuid = truncateUuid(data.uuid);
|
|
576
|
+
const name = data.name || 'Unnamed episode';
|
|
577
|
+
lines[0] = `✓ Episode added: "${name}" (id: ${uuid})`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (data.entities_extracted !== undefined || data.facts_extracted !== undefined) {
|
|
581
|
+
const entities = data.entities_extracted ?? 0;
|
|
582
|
+
const facts = data.facts_extracted ?? 0;
|
|
583
|
+
lines.push(` Extracted: ${entities} entities, ${facts} facts`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return lines.join('\n');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Format get_status response
|
|
591
|
+
* Output: Knowledge Graph Status: OK
|
|
592
|
+
* Connected to neo4j database
|
|
593
|
+
*/
|
|
594
|
+
export function formatGetStatus(data: unknown, _options: FormatOptions): string {
|
|
595
|
+
if (!isGetStatusResponse(data)) {
|
|
596
|
+
throw new Error('Invalid data format for get_status');
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const status = data.status.toUpperCase();
|
|
600
|
+
const lines: string[] = [`Knowledge Graph Status: ${status}`];
|
|
601
|
+
lines.push(data.message);
|
|
602
|
+
|
|
603
|
+
// Include legacy stats if available
|
|
604
|
+
if (data.entity_count !== undefined && data.episode_count !== undefined) {
|
|
605
|
+
let statsLine = `Entities: ${data.entity_count} | Episodes: ${data.episode_count}`;
|
|
606
|
+
if (data.last_updated) {
|
|
607
|
+
statsLine += ` | Last update: ${relativeTime(data.last_updated)}`;
|
|
608
|
+
}
|
|
609
|
+
lines.push(statsLine);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return lines.join('\n');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Format delete_episode / delete_entity_edge response
|
|
617
|
+
* Output: ✓ Deleted: ...uuid8
|
|
618
|
+
* or: ✗ Delete failed: message
|
|
619
|
+
*/
|
|
620
|
+
export function formatDelete(data: unknown, _options: FormatOptions): string {
|
|
621
|
+
if (!isDeleteResponse(data)) {
|
|
622
|
+
throw new Error('Invalid data format for delete');
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (data.success) {
|
|
626
|
+
const uuid = data.uuid ? truncateUuid(data.uuid) : 'unknown';
|
|
627
|
+
return `✓ Deleted: ${uuid}`;
|
|
628
|
+
}
|
|
629
|
+
const message = data.message || 'Unknown error';
|
|
630
|
+
return `✗ Delete failed: ${message}`;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Format clear_graph response
|
|
635
|
+
* Output: ✓ Knowledge graph cleared
|
|
636
|
+
* Removed: N entities, M episodes
|
|
637
|
+
*/
|
|
638
|
+
export function formatClearGraph(data: unknown, _options: FormatOptions): string {
|
|
639
|
+
if (!isClearGraphResponse(data)) {
|
|
640
|
+
throw new Error('Invalid data format for clear_graph');
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (!data.success) {
|
|
644
|
+
return '✗ Clear graph failed';
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const lines: string[] = ['✓ Knowledge graph cleared'];
|
|
648
|
+
|
|
649
|
+
if (data.deleted_entities !== undefined || data.deleted_episodes !== undefined) {
|
|
650
|
+
const entities = data.deleted_entities ?? 0;
|
|
651
|
+
const episodes = data.deleted_episodes ?? 0;
|
|
652
|
+
lines.push(` Removed: ${entities} entities, ${episodes} episodes`);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return lines.join('\n');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// ============================================================================
|
|
659
|
+
// Feature 009: Memory Decay Formatters
|
|
660
|
+
// ============================================================================
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Format get_knowledge_health response
|
|
664
|
+
* Output: Memory Lifecycle Health:
|
|
665
|
+
* Lifecycle States: Active, Dormant, Archived, Expired, Soft Deleted, Permanent
|
|
666
|
+
* Aggregates: Total, Avg Decay, Avg Importance, Avg Stability
|
|
667
|
+
* Age Distribution: < 7d, 7-30d, 30-90d, > 90d
|
|
668
|
+
* Last Maintenance: timestamp, duration, processed, transitions
|
|
669
|
+
*/
|
|
670
|
+
export function formatHealthMetrics(data: unknown, _options: FormatOptions): string {
|
|
671
|
+
if (!isHealthMetricsResponse(data)) {
|
|
672
|
+
throw new Error('Invalid data format for get_knowledge_health');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const lines: string[] = ['Memory Lifecycle Health:'];
|
|
676
|
+
|
|
677
|
+
// Lifecycle states
|
|
678
|
+
lines.push('\nLifecycle States:');
|
|
679
|
+
lines.push(` Active: ${data.states.active.toLocaleString()}`);
|
|
680
|
+
lines.push(` Dormant: ${data.states.dormant.toLocaleString()}`);
|
|
681
|
+
lines.push(` Archived: ${data.states.archived.toLocaleString()}`);
|
|
682
|
+
lines.push(` Expired: ${data.states.expired.toLocaleString()}`);
|
|
683
|
+
lines.push(` Soft Deleted: ${data.states.soft_deleted.toLocaleString()}`);
|
|
684
|
+
lines.push(` Permanent: ${data.states.permanent.toLocaleString()}`);
|
|
685
|
+
|
|
686
|
+
// Aggregates
|
|
687
|
+
lines.push('\nAggregates:');
|
|
688
|
+
lines.push(` Total: ${data.aggregates.total.toLocaleString()}`);
|
|
689
|
+
lines.push(` Avg Decay: ${data.aggregates.average_decay.toFixed(3)}`);
|
|
690
|
+
lines.push(` Avg Importance: ${data.aggregates.average_importance.toFixed(2)}/5`);
|
|
691
|
+
lines.push(` Avg Stability: ${data.aggregates.average_stability.toFixed(2)}/5`);
|
|
692
|
+
|
|
693
|
+
// Age distribution
|
|
694
|
+
lines.push('\nAge Distribution:');
|
|
695
|
+
lines.push(` < 7 days: ${data.age_distribution.under_7_days.toLocaleString()}`);
|
|
696
|
+
lines.push(` 7-30 days: ${data.age_distribution.days_7_to_30.toLocaleString()}`);
|
|
697
|
+
lines.push(` 30-90 days: ${data.age_distribution.days_30_to_90.toLocaleString()}`);
|
|
698
|
+
lines.push(` > 90 days: ${data.age_distribution.over_90_days.toLocaleString()}`);
|
|
699
|
+
|
|
700
|
+
// Maintenance info
|
|
701
|
+
if (data.maintenance.last_run) {
|
|
702
|
+
lines.push('\nLast Maintenance:');
|
|
703
|
+
lines.push(` Run: ${relativeTime(data.maintenance.last_run)}`);
|
|
704
|
+
lines.push(` Duration: ${data.maintenance.duration_seconds.toFixed(1)}s`);
|
|
705
|
+
lines.push(` Processed: ${data.maintenance.processed.toLocaleString()}`);
|
|
706
|
+
lines.push(` Transitions: ${data.maintenance.transitions.toLocaleString()}`);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return lines.join('\n');
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Format run_decay_maintenance response
|
|
714
|
+
* Output: ✓ Maintenance completed successfully
|
|
715
|
+
* Processed: N memories
|
|
716
|
+
* Duration: X.Xs
|
|
717
|
+
* Classification: Found/Classified/Failed
|
|
718
|
+
* Decay scores updated: N
|
|
719
|
+
* State Transitions: ACTIVE→DORMANT, etc.
|
|
720
|
+
* Purged: N soft-deleted memories
|
|
721
|
+
*/
|
|
722
|
+
export function formatMaintenanceResult(data: unknown, _options: FormatOptions): string {
|
|
723
|
+
if (!isMaintenanceResultResponse(data)) {
|
|
724
|
+
throw new Error('Invalid data format for run_decay_maintenance');
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const lines: string[] = [];
|
|
728
|
+
|
|
729
|
+
if (data.success) {
|
|
730
|
+
lines.push('✓ Maintenance completed successfully');
|
|
731
|
+
} else {
|
|
732
|
+
lines.push(`✗ Maintenance failed: ${data.error || 'Unknown error'}`);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
lines.push(` Processed: ${data.memories_processed.toLocaleString()} memories`);
|
|
736
|
+
lines.push(` Duration: ${data.duration_seconds.toFixed(1)}s`);
|
|
737
|
+
|
|
738
|
+
// Classification results
|
|
739
|
+
if (data.nodes_classified.classified > 0) {
|
|
740
|
+
lines.push('\nClassification:');
|
|
741
|
+
lines.push(` Found: ${data.nodes_classified.found}`);
|
|
742
|
+
lines.push(` Classified: ${data.nodes_classified.classified} (${data.nodes_classified.using_llm ? 'LLM' : 'default'})`);
|
|
743
|
+
if (data.nodes_classified.failed > 0) {
|
|
744
|
+
lines.push(` Failed: ${data.nodes_classified.failed}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Decay scores
|
|
749
|
+
if (data.decay_scores_updated > 0) {
|
|
750
|
+
lines.push(`\nDecay scores updated: ${data.decay_scores_updated.toLocaleString()}`);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// State transitions
|
|
754
|
+
const st = data.state_transitions;
|
|
755
|
+
const total = st.active_to_dormant + st.dormant_to_archived + st.archived_to_expired + st.expired_to_soft_deleted;
|
|
756
|
+
if (total > 0) {
|
|
757
|
+
lines.push('\nState Transitions:');
|
|
758
|
+
lines.push(` ACTIVE → DORMANT: ${st.active_to_dormant}`);
|
|
759
|
+
lines.push(` DORMANT → ARCHIVED: ${st.dormant_to_archived}`);
|
|
760
|
+
lines.push(` ARCHIVED → EXPIRED: ${st.archived_to_expired}`);
|
|
761
|
+
lines.push(` EXPIRED → SOFT_DEL: ${st.expired_to_soft_deleted}`);
|
|
762
|
+
lines.push(` Total: ${total}`);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Purged
|
|
766
|
+
if (data.soft_deleted_purged > 0) {
|
|
767
|
+
lines.push(`\nPurged: ${data.soft_deleted_purged.toLocaleString()} soft-deleted memories`);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (data.completed_at) {
|
|
771
|
+
lines.push(`\nCompleted: ${relativeTime(data.completed_at)}`);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return lines.join('\n');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Format classify_memory response
|
|
779
|
+
* Output: Memory Classification:
|
|
780
|
+
* Importance: 3/3 ★★★☆☆
|
|
781
|
+
* Stability: 4/5 ★★★★☆
|
|
782
|
+
* Source: llm
|
|
783
|
+
* Status: Subject to decay / PERMANENT (exempt from decay)
|
|
784
|
+
*/
|
|
785
|
+
export function formatClassification(data: unknown, _options: FormatOptions): string {
|
|
786
|
+
if (!isClassificationResponse(data)) {
|
|
787
|
+
throw new Error('Invalid data format for classify_memory');
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const lines: string[] = ['Memory Classification:'];
|
|
791
|
+
lines.push(` Importance: ${data.importance}/5 ${'★'.repeat(data.importance)}${'☆'.repeat(5 - data.importance)}`);
|
|
792
|
+
lines.push(` Stability: ${data.stability}/5 ${'★'.repeat(data.stability)}${'☆'.repeat(5 - data.stability)}`);
|
|
793
|
+
lines.push(` Source: ${data.classification_source}`);
|
|
794
|
+
|
|
795
|
+
if (data.is_permanent) {
|
|
796
|
+
lines.push(` Status: PERMANENT (exempt from decay)`);
|
|
797
|
+
} else {
|
|
798
|
+
lines.push(` Status: Subject to decay`);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return lines.join('\n');
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Format recover_soft_deleted response
|
|
806
|
+
* Output: ✓ Memory recovered successfully
|
|
807
|
+
* Name: Memory Name
|
|
808
|
+
* UUID: ...uuid8
|
|
809
|
+
* New State: ARCHIVED
|
|
810
|
+
*/
|
|
811
|
+
export function formatRecovery(data: unknown, _options: FormatOptions): string {
|
|
812
|
+
if (!isRecoveryResponse(data)) {
|
|
813
|
+
throw new Error('Invalid data format for recover_soft_deleted');
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const lines: string[] = [`✓ ${data.message}`];
|
|
817
|
+
lines.push(` Name: ${data.name}`);
|
|
818
|
+
lines.push(` UUID: ...${data.uuid.slice(-8)}`);
|
|
819
|
+
lines.push(` New State: ${data.new_state}`);
|
|
820
|
+
|
|
821
|
+
return lines.join('\n');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Format investigate_entity response
|
|
826
|
+
* Output: Entity: [Type] Name - Summary
|
|
827
|
+
* Connections:
|
|
828
|
+
* 1. [RELATION] Target [Type] (hop N)
|
|
829
|
+
* Metadata: depth=X, connections=Y, cycles=Z
|
|
830
|
+
*/
|
|
831
|
+
export function formatInvestigateEntity(data: unknown, _options: FormatOptions): string {
|
|
832
|
+
if (!isInvestigateEntityResponse(data)) {
|
|
833
|
+
throw new Error('Invalid data format for investigate_entity');
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const { entity, connections, metadata, warning } = data;
|
|
837
|
+
const lines: string[] = [];
|
|
838
|
+
|
|
839
|
+
// Format the primary entity
|
|
840
|
+
const entityType = entity.labels?.[0] || 'Entity';
|
|
841
|
+
const summary = entity.summary ? truncateText(entity.summary, 120) : '';
|
|
842
|
+
lines.push(`Entity: [${entityType}] ${entity.name}${summary ? ' - ' + summary : ''}`);
|
|
843
|
+
|
|
844
|
+
// Format connections
|
|
845
|
+
if (connections.length === 0) {
|
|
846
|
+
lines.push('No connections found.');
|
|
847
|
+
} else {
|
|
848
|
+
lines.push(`\nConnections (${connections.length}):`);
|
|
849
|
+
connections.forEach((conn, index) => {
|
|
850
|
+
const targetType = conn.target_entity.labels?.[0] || 'Entity';
|
|
851
|
+
const relation = conn.relationship.toUpperCase();
|
|
852
|
+
const directionSymbol = conn.direction === 'outbound' ? '→' : conn.direction === 'inbound' ? '←' : '↔';
|
|
853
|
+
const hopInfo = conn.hop_distance > 1 ? ` (hop ${conn.hop_distance})` : '';
|
|
854
|
+
|
|
855
|
+
let connLine = ` ${index + 1}. [${relation}] ${directionSymbol} ${conn.target_entity.name} [${targetType}]${hopInfo}`;
|
|
856
|
+
|
|
857
|
+
if (conn.fact) {
|
|
858
|
+
connLine += `\n Fact: ${truncateText(conn.fact, 100)}`;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (conn.confidence !== undefined) {
|
|
862
|
+
connLine += ` (${(conn.confidence * 100).toFixed(0)}% confidence)`;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
lines.push(connLine);
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Format metadata
|
|
870
|
+
lines.push('\nMetadata:');
|
|
871
|
+
lines.push(` Depth explored: ${metadata.depth_explored}`);
|
|
872
|
+
lines.push(` Connections: ${metadata.connections_returned}/${metadata.total_connections_explored}`);
|
|
873
|
+
if (metadata.cycles_detected > 0) {
|
|
874
|
+
lines.push(` Cycles detected: ${metadata.cycles_detected} (pruned: ${metadata.cycles_pruned})`);
|
|
875
|
+
}
|
|
876
|
+
if (metadata.entities_skipped > 0) {
|
|
877
|
+
lines.push(` Entities skipped: ${metadata.entities_skipped}`);
|
|
878
|
+
}
|
|
879
|
+
lines.push(` Query duration: ${metadata.query_duration_ms}ms`);
|
|
880
|
+
if (metadata.max_connections_exceeded) {
|
|
881
|
+
lines.push(` ⚠ Max connections limit exceeded`);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Add warning if present
|
|
885
|
+
if (warning) {
|
|
886
|
+
lines.push(`\n⚠ Warning: ${warning}`);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return lines.join('\n');
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// ============================================================================
|
|
893
|
+
// Main Entry Point (T021)
|
|
894
|
+
// ============================================================================
|
|
895
|
+
|
|
896
|
+
// Register all built-in formatters
|
|
897
|
+
registerFormatter('search_nodes', formatSearchNodes);
|
|
898
|
+
registerFormatter('search_memory_nodes', formatSearchNodes);
|
|
899
|
+
registerFormatter('search_facts', formatSearchFacts);
|
|
900
|
+
registerFormatter('search_memory_facts', formatSearchFacts);
|
|
901
|
+
registerFormatter('get_episodes', formatGetEpisodes);
|
|
902
|
+
registerFormatter('add_memory', formatAddMemory);
|
|
903
|
+
registerFormatter('add_episode', formatAddMemory);
|
|
904
|
+
registerFormatter('get_status', formatGetStatus);
|
|
905
|
+
registerFormatter('delete_episode', formatDelete);
|
|
906
|
+
registerFormatter('delete_entity_edge', formatDelete);
|
|
907
|
+
registerFormatter('clear_graph', formatClearGraph);
|
|
908
|
+
// Feature 009: Memory decay formatters
|
|
909
|
+
registerFormatter('get_knowledge_health', formatHealthMetrics);
|
|
910
|
+
registerFormatter('run_decay_maintenance', formatMaintenanceResult);
|
|
911
|
+
registerFormatter('classify_memory', formatClassification);
|
|
912
|
+
registerFormatter('recover_soft_deleted', formatRecovery);
|
|
913
|
+
// Feature 020: Investigative search
|
|
914
|
+
registerFormatter('investigate_entity', formatInvestigateEntity);
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Main entry point for formatting MCP responses.
|
|
918
|
+
* Routes to the appropriate formatter based on operation name.
|
|
919
|
+
* Falls back to JSON on unknown operations or errors.
|
|
920
|
+
*/
|
|
921
|
+
export function formatOutput(
|
|
922
|
+
operation: string,
|
|
923
|
+
data: unknown,
|
|
924
|
+
options?: FormatOptions
|
|
925
|
+
): FormatResult {
|
|
926
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
927
|
+
const startTime = performance.now();
|
|
928
|
+
|
|
929
|
+
// Calculate raw size
|
|
930
|
+
const rawJson = JSON.stringify(data, null, 2);
|
|
931
|
+
const rawBytes = new TextEncoder().encode(rawJson).length;
|
|
932
|
+
|
|
933
|
+
// Try to find and execute formatter
|
|
934
|
+
const formatter = formatterRegistry.get(operation);
|
|
935
|
+
|
|
936
|
+
if (!formatter) {
|
|
937
|
+
// Unknown operation - fallback to JSON
|
|
938
|
+
return {
|
|
939
|
+
output: rawJson,
|
|
940
|
+
usedFallback: true,
|
|
941
|
+
error: `No formatter registered for operation: ${operation}`,
|
|
942
|
+
metrics: opts.collectMetrics
|
|
943
|
+
? {
|
|
944
|
+
rawBytes,
|
|
945
|
+
compactBytes: rawBytes,
|
|
946
|
+
savingsPercent: 0,
|
|
947
|
+
processingTimeMs: performance.now() - startTime,
|
|
948
|
+
}
|
|
949
|
+
: undefined,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
try {
|
|
954
|
+
const output = formatter(data, opts);
|
|
955
|
+
const compactBytes = new TextEncoder().encode(output).length;
|
|
956
|
+
const processingTimeMs = performance.now() - startTime;
|
|
957
|
+
const savingsPercent = rawBytes > 0 ? ((rawBytes - compactBytes) / rawBytes) * 100 : 0;
|
|
958
|
+
|
|
959
|
+
// Log slow transformations
|
|
960
|
+
if (processingTimeMs >= 50) {
|
|
961
|
+
logTransformationSuccess(operation, rawBytes, processingTimeMs).catch(() => {});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return {
|
|
965
|
+
output,
|
|
966
|
+
usedFallback: false,
|
|
967
|
+
metrics: opts.collectMetrics
|
|
968
|
+
? {
|
|
969
|
+
rawBytes,
|
|
970
|
+
compactBytes,
|
|
971
|
+
savingsPercent,
|
|
972
|
+
processingTimeMs,
|
|
973
|
+
}
|
|
974
|
+
: undefined,
|
|
975
|
+
};
|
|
976
|
+
} catch (error) {
|
|
977
|
+
const processingTimeMs = performance.now() - startTime;
|
|
978
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
979
|
+
|
|
980
|
+
// Log transformation failure
|
|
981
|
+
logTransformationFailure(operation, rawBytes, errorMessage, processingTimeMs).catch(() => {});
|
|
982
|
+
|
|
983
|
+
return {
|
|
984
|
+
output: rawJson,
|
|
985
|
+
usedFallback: true,
|
|
986
|
+
error: errorMessage,
|
|
987
|
+
metrics: opts.collectMetrics
|
|
988
|
+
? {
|
|
989
|
+
rawBytes,
|
|
990
|
+
compactBytes: rawBytes,
|
|
991
|
+
savingsPercent: 0,
|
|
992
|
+
processingTimeMs,
|
|
993
|
+
}
|
|
994
|
+
: undefined,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|