ahok-skill 1.3.1

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.
Files changed (141) hide show
  1. package/.prettierrc +8 -0
  2. package/Dockerfile +59 -0
  3. package/RAW_SKILL.md +219 -0
  4. package/README.md +277 -0
  5. package/SKILL.md +58 -0
  6. package/bin/opm.js +268 -0
  7. package/data/openmemory.sqlite +0 -0
  8. package/data/openmemory.sqlite-shm +0 -0
  9. package/data/openmemory.sqlite-wal +0 -0
  10. package/dist/ai/graph.js +293 -0
  11. package/dist/ai/mcp.js +397 -0
  12. package/dist/cli.js +78 -0
  13. package/dist/core/cfg.js +87 -0
  14. package/dist/core/db.js +636 -0
  15. package/dist/core/memory.js +116 -0
  16. package/dist/core/migrate.js +227 -0
  17. package/dist/core/models.js +105 -0
  18. package/dist/core/telemetry.js +57 -0
  19. package/dist/core/types.js +2 -0
  20. package/dist/core/vector/postgres.js +52 -0
  21. package/dist/core/vector/valkey.js +246 -0
  22. package/dist/core/vector_store.js +2 -0
  23. package/dist/index.js +44 -0
  24. package/dist/memory/decay.js +301 -0
  25. package/dist/memory/embed.js +675 -0
  26. package/dist/memory/hsg.js +959 -0
  27. package/dist/memory/reflect.js +131 -0
  28. package/dist/memory/user_summary.js +99 -0
  29. package/dist/migrate.js +9 -0
  30. package/dist/ops/compress.js +255 -0
  31. package/dist/ops/dynamics.js +189 -0
  32. package/dist/ops/extract.js +333 -0
  33. package/dist/ops/ingest.js +214 -0
  34. package/dist/server/index.js +109 -0
  35. package/dist/server/middleware/auth.js +137 -0
  36. package/dist/server/routes/auth.js +186 -0
  37. package/dist/server/routes/compression.js +108 -0
  38. package/dist/server/routes/dashboard.js +399 -0
  39. package/dist/server/routes/docs.js +241 -0
  40. package/dist/server/routes/dynamics.js +312 -0
  41. package/dist/server/routes/ide.js +280 -0
  42. package/dist/server/routes/index.js +33 -0
  43. package/dist/server/routes/keys.js +132 -0
  44. package/dist/server/routes/langgraph.js +61 -0
  45. package/dist/server/routes/memory.js +213 -0
  46. package/dist/server/routes/sources.js +140 -0
  47. package/dist/server/routes/system.js +63 -0
  48. package/dist/server/routes/temporal.js +293 -0
  49. package/dist/server/routes/users.js +101 -0
  50. package/dist/server/routes/vercel.js +57 -0
  51. package/dist/server/server.js +211 -0
  52. package/dist/server.js +3 -0
  53. package/dist/sources/base.js +223 -0
  54. package/dist/sources/github.js +171 -0
  55. package/dist/sources/google_drive.js +166 -0
  56. package/dist/sources/google_sheets.js +112 -0
  57. package/dist/sources/google_slides.js +139 -0
  58. package/dist/sources/index.js +34 -0
  59. package/dist/sources/notion.js +165 -0
  60. package/dist/sources/onedrive.js +143 -0
  61. package/dist/sources/web_crawler.js +166 -0
  62. package/dist/temporal_graph/index.js +20 -0
  63. package/dist/temporal_graph/query.js +240 -0
  64. package/dist/temporal_graph/store.js +116 -0
  65. package/dist/temporal_graph/timeline.js +241 -0
  66. package/dist/temporal_graph/types.js +2 -0
  67. package/dist/utils/chunking.js +60 -0
  68. package/dist/utils/index.js +31 -0
  69. package/dist/utils/keyword.js +94 -0
  70. package/dist/utils/text.js +120 -0
  71. package/nodemon.json +7 -0
  72. package/package.json +50 -0
  73. package/references/api_reference.md +66 -0
  74. package/references/examples.md +45 -0
  75. package/src/ai/graph.ts +363 -0
  76. package/src/ai/mcp.ts +494 -0
  77. package/src/cli.ts +94 -0
  78. package/src/core/cfg.ts +110 -0
  79. package/src/core/db.ts +1052 -0
  80. package/src/core/memory.ts +99 -0
  81. package/src/core/migrate.ts +302 -0
  82. package/src/core/models.ts +107 -0
  83. package/src/core/telemetry.ts +47 -0
  84. package/src/core/types.ts +130 -0
  85. package/src/core/vector/postgres.ts +61 -0
  86. package/src/core/vector/valkey.ts +261 -0
  87. package/src/core/vector_store.ts +9 -0
  88. package/src/index.ts +5 -0
  89. package/src/memory/decay.ts +427 -0
  90. package/src/memory/embed.ts +707 -0
  91. package/src/memory/hsg.ts +1245 -0
  92. package/src/memory/reflect.ts +158 -0
  93. package/src/memory/user_summary.ts +110 -0
  94. package/src/migrate.ts +8 -0
  95. package/src/ops/compress.ts +296 -0
  96. package/src/ops/dynamics.ts +272 -0
  97. package/src/ops/extract.ts +360 -0
  98. package/src/ops/ingest.ts +286 -0
  99. package/src/server/index.ts +159 -0
  100. package/src/server/middleware/auth.ts +156 -0
  101. package/src/server/routes/auth.ts +223 -0
  102. package/src/server/routes/compression.ts +106 -0
  103. package/src/server/routes/dashboard.ts +420 -0
  104. package/src/server/routes/docs.ts +380 -0
  105. package/src/server/routes/dynamics.ts +516 -0
  106. package/src/server/routes/ide.ts +283 -0
  107. package/src/server/routes/index.ts +32 -0
  108. package/src/server/routes/keys.ts +131 -0
  109. package/src/server/routes/langgraph.ts +71 -0
  110. package/src/server/routes/memory.ts +440 -0
  111. package/src/server/routes/sources.ts +111 -0
  112. package/src/server/routes/system.ts +68 -0
  113. package/src/server/routes/temporal.ts +335 -0
  114. package/src/server/routes/users.ts +111 -0
  115. package/src/server/routes/vercel.ts +55 -0
  116. package/src/server/server.js +215 -0
  117. package/src/server.ts +1 -0
  118. package/src/sources/base.ts +257 -0
  119. package/src/sources/github.ts +156 -0
  120. package/src/sources/google_drive.ts +144 -0
  121. package/src/sources/google_sheets.ts +85 -0
  122. package/src/sources/google_slides.ts +115 -0
  123. package/src/sources/index.ts +19 -0
  124. package/src/sources/notion.ts +148 -0
  125. package/src/sources/onedrive.ts +131 -0
  126. package/src/sources/web_crawler.ts +161 -0
  127. package/src/temporal_graph/index.ts +4 -0
  128. package/src/temporal_graph/query.ts +299 -0
  129. package/src/temporal_graph/store.ts +156 -0
  130. package/src/temporal_graph/timeline.ts +319 -0
  131. package/src/temporal_graph/types.ts +41 -0
  132. package/src/utils/chunking.ts +66 -0
  133. package/src/utils/index.ts +25 -0
  134. package/src/utils/keyword.ts +137 -0
  135. package/src/utils/text.ts +115 -0
  136. package/tests/test_api_workspace_management.ts +413 -0
  137. package/tests/test_bulk_delete.ts +267 -0
  138. package/tests/test_omnibus.ts +166 -0
  139. package/tests/test_workspace_management.ts +278 -0
  140. package/tests/verify.ts +104 -0
  141. package/tsconfig.json +15 -0
@@ -0,0 +1,283 @@
1
+ import { q } from "../../core/db";
2
+ import { add_hsg_memory, hsg_query } from "../../memory/hsg";
3
+ import { update_user_summary } from "../../memory/user_summary";
4
+ import { j, p } from "../../utils";
5
+ import * as crypto from "crypto";
6
+ export function ide(app: any) {
7
+ app.post("/api/ide/events", async (req: any, res: any) => {
8
+ try {
9
+ const event_type = req.body.event_type;
10
+ const file_path = req.body.file_path || "unknown";
11
+ const content = req.body.content || "";
12
+ const session_id = req.body.session_id || "default";
13
+ const metadata = req.body.metadata || {};
14
+ const user_id = req.body.user_id || "anonymous";
15
+
16
+ if (!event_type)
17
+ return res.status(400).json({ err: "event_type_required" });
18
+
19
+ let memory_content = "";
20
+ if (event_type === "open") {
21
+ memory_content = `Opened file: ${file_path}`;
22
+ } else if (event_type === "save") {
23
+ if (content) {
24
+ memory_content = `Saved file: ${file_path}\n\n${content}`;
25
+ } else {
26
+ memory_content = `Saved file: ${file_path}`;
27
+ }
28
+ } else if (event_type === "close") {
29
+ memory_content = `Closed file: ${file_path}`;
30
+ } else {
31
+ memory_content = `[${event_type}] ${file_path}\n${content}`.trim();
32
+ }
33
+
34
+ const full_metadata = {
35
+ ...metadata,
36
+ ide_event_type: event_type,
37
+ ide_file_path: file_path,
38
+ ide_session_id: session_id,
39
+ ide_timestamp: Date.now(),
40
+ ide_mode: true,
41
+ };
42
+
43
+ const result = await add_hsg_memory(
44
+ memory_content,
45
+ undefined,
46
+ full_metadata,
47
+ user_id,
48
+ );
49
+
50
+ // Update user summary asynchronously
51
+ if (user_id && user_id !== "anonymous") {
52
+ update_user_summary(user_id).catch(err =>
53
+ console.error("[IDE] Failed to update user summary:", err)
54
+ );
55
+ }
56
+
57
+ res.json({
58
+ success: true,
59
+ memory_id: result.id,
60
+ primary_sector: result.primary_sector,
61
+ sectors: result.sectors,
62
+ });
63
+ } catch (err) {
64
+ console.error("[IDE] Error storing IDE event:", err);
65
+ res.status(500).json({ err: "internal" });
66
+ }
67
+ });
68
+
69
+ app.post("/api/ide/context", async (req: any, res: any) => {
70
+ try {
71
+ const query = req.body.query;
72
+ const k = req.body.k || req.body.limit || 5;
73
+ const session_id = req.body.session_id;
74
+ const file_path = req.body.file_path;
75
+
76
+ if (!query) return res.status(400).json({ err: "query_required" });
77
+
78
+ const results = await hsg_query(query, k);
79
+
80
+ let filtered = results;
81
+
82
+ if (session_id) {
83
+ filtered = [];
84
+ for (const r of results) {
85
+ const mem = await q.get_mem.get(r.id);
86
+ if (mem) {
87
+ const meta = p(mem.meta);
88
+ if (meta && meta.ide_session_id === session_id) {
89
+ filtered.push(r);
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ if (file_path) {
96
+ filtered = filtered.filter((r: any) =>
97
+ r.content.includes(file_path),
98
+ );
99
+ }
100
+
101
+ const formatted = filtered.map((r: any) => ({
102
+ memory_id: r.id,
103
+ content: r.content,
104
+ primary_sector: r.primary_sector,
105
+ sectors: r.sectors,
106
+ score: r.score,
107
+ salience: r.salience,
108
+ last_seen_at: r.last_seen_at,
109
+ path: r.path,
110
+ }));
111
+
112
+ res.json({
113
+ success: true,
114
+ memories: formatted,
115
+ total: formatted.length,
116
+ query: query,
117
+ });
118
+ } catch (err) {
119
+ console.error("[IDE] Error retrieving IDE context:", err);
120
+ res.status(500).json({ err: "internal" });
121
+ }
122
+ });
123
+
124
+ app.post("/api/ide/session/start", async (req: any, res: any) => {
125
+ try {
126
+ const user_id = req.body.user_id || "anonymous";
127
+ const project_name = req.body.project_name || "unknown";
128
+ const ide_name = req.body.ide_name || "unknown";
129
+
130
+ const session_id = `session_${Date.now()}_${crypto.randomBytes(7).toString("hex")}`;
131
+ const now_ts = Date.now();
132
+
133
+ const content = `Session started: ${user_id} in ${project_name} using ${ide_name}`;
134
+
135
+ const metadata = {
136
+ ide_session_id: session_id,
137
+ ide_user_id: user_id,
138
+ ide_project_name: project_name,
139
+ ide_name: ide_name,
140
+ session_start_time: now_ts,
141
+ session_type: "ide_session",
142
+ ide_mode: true,
143
+ };
144
+
145
+ const result = await add_hsg_memory(content, undefined, metadata, user_id);
146
+
147
+ if (user_id && user_id !== "anonymous") {
148
+ update_user_summary(user_id).catch(err =>
149
+ console.error("[IDE] Failed to update summary on session start:", err)
150
+ );
151
+ }
152
+
153
+ res.json({
154
+ success: true,
155
+ session_id: session_id,
156
+ memory_id: result.id,
157
+ started_at: now_ts,
158
+ user_id: user_id,
159
+ project_name: project_name,
160
+ ide_name: ide_name,
161
+ });
162
+ } catch (err) {
163
+ console.error("[IDE] Error starting IDE session:", err);
164
+ res.status(500).json({ err: "internal" });
165
+ }
166
+ });
167
+
168
+ app.post("/api/ide/session/end", async (req: any, res: any) => {
169
+ try {
170
+ const session_id = req.body.session_id;
171
+ const user_id = req.body.user_id || "anonymous";
172
+
173
+ if (!session_id)
174
+ return res.status(400).json({ err: "session_id_required" });
175
+
176
+ const now_ts = Date.now();
177
+
178
+ const all_memories = await q.all_mem.all(10000, 0);
179
+ const session_memories = all_memories.filter((m: any) => {
180
+ try {
181
+ const meta = p(m.meta);
182
+ return meta && meta.ide_session_id === session_id;
183
+ } catch {
184
+ return false;
185
+ }
186
+ });
187
+
188
+ const total_events = session_memories.length;
189
+ const sectors: Record<string, number> = {};
190
+ const files = new Set<string>();
191
+
192
+ for (const m of session_memories) {
193
+ sectors[m.primary_sector] =
194
+ (sectors[m.primary_sector] || 0) + 1;
195
+ try {
196
+ const meta = p(m.meta);
197
+ if (
198
+ meta &&
199
+ meta.ide_file_path &&
200
+ meta.ide_file_path !== "unknown"
201
+ ) {
202
+ files.add(meta.ide_file_path);
203
+ }
204
+ } catch { }
205
+ }
206
+
207
+ const summary = `Session ${session_id} ended. Events: ${total_events}, Files: ${files.size}, Sectors: ${j(sectors)}`;
208
+
209
+ const metadata = {
210
+ ide_session_id: session_id,
211
+ session_end_time: now_ts,
212
+ session_type: "ide_session_end",
213
+ total_events: total_events,
214
+ sectors_distribution: sectors,
215
+ files_touched: Array.from(files),
216
+ ide_mode: true,
217
+ };
218
+
219
+ const result = await add_hsg_memory(summary, undefined, metadata, user_id);
220
+
221
+ if (user_id && user_id !== "anonymous") {
222
+ update_user_summary(user_id).catch(err =>
223
+ console.error("[IDE] Failed to update summary on session end:", err)
224
+ );
225
+ }
226
+
227
+ res.json({
228
+ success: true,
229
+ session_id: session_id,
230
+ ended_at: now_ts,
231
+ summary_memory_id: result.id,
232
+ statistics: {
233
+ total_events: total_events,
234
+ sectors: sectors,
235
+ unique_files: files.size,
236
+ files: Array.from(files),
237
+ },
238
+ });
239
+ } catch (err) {
240
+ console.error("[IDE] Error ending IDE session:", err);
241
+ res.status(500).json({ err: "internal" });
242
+ }
243
+ });
244
+
245
+ app.get("/api/ide/patterns/:session_id", async (req: any, res: any) => {
246
+ try {
247
+ const session_id = req.params.session_id;
248
+
249
+ if (!session_id)
250
+ return res.status(400).json({ err: "session_id_required" });
251
+
252
+ const all_memories = await q.all_mem.all(10000, 0);
253
+
254
+ const procedural = all_memories.filter((m: any) => {
255
+ if (m.primary_sector !== "procedural") return false;
256
+ try {
257
+ const meta = p(m.meta);
258
+ return meta && meta.ide_session_id === session_id;
259
+ } catch {
260
+ return false;
261
+ }
262
+ });
263
+
264
+ const patterns = procedural.map((m: any) => ({
265
+ pattern_id: m.id,
266
+ description: m.content,
267
+ salience: m.salience,
268
+ detected_at: m.created_at,
269
+ last_reinforced: m.last_seen_at,
270
+ }));
271
+
272
+ res.json({
273
+ success: true,
274
+ session_id: session_id,
275
+ pattern_count: patterns.length,
276
+ patterns: patterns,
277
+ });
278
+ } catch (err) {
279
+ console.error("[IDE] Error detecting patterns:", err);
280
+ res.status(500).json({ err: "internal" });
281
+ }
282
+ });
283
+ }
@@ -0,0 +1,32 @@
1
+ import { sys } from "./system";
2
+ import { mem } from "./memory";
3
+ import { dynroutes } from "./dynamics";
4
+ import { ide } from "./ide";
5
+ import { compression } from "./compression";
6
+ import { lg } from "./langgraph";
7
+ import { usr } from "./users";
8
+ import { temporal } from "./temporal";
9
+ import { dash } from "./dashboard";
10
+ import { vercel } from "./vercel";
11
+ import { src } from "./sources";
12
+ import { auth } from "./auth";
13
+ import { docs_route } from "./docs";
14
+ import { keys } from "./keys";
15
+
16
+ export function routes(app: any) {
17
+ sys(app);
18
+ mem(app);
19
+ dynroutes(app);
20
+ ide(app);
21
+ compression(app);
22
+ lg(app);
23
+ usr(app);
24
+ temporal(app);
25
+ dash(app);
26
+ vercel(app);
27
+ src(app);
28
+ auth(app);
29
+ docs_route(app);
30
+ keys(app);
31
+ }
32
+
@@ -0,0 +1,131 @@
1
+ import { q } from "../../core/db";
2
+ import crypto from "crypto";
3
+ import { verifyToken } from "@clerk/backend";
4
+
5
+ async function verifyClerkToken(req: any): Promise<any | null> {
6
+ const token = req.headers.authorization?.replace("Bearer ", "");
7
+ if (!token) return null;
8
+ try {
9
+ const session = await verifyToken(token, { secretKey: process.env.CLERK_SECRET_KEY });
10
+ const user = await q.get_user_by_clerk_id.get(session.sub);
11
+ return user;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ export function keys(app: any) {
18
+ app.get("/keys", async (req: any, res: any) => {
19
+ // Try API key auth first, then Clerk JWT
20
+ let user = req.user;
21
+ if (!user?.user_id) {
22
+ user = await verifyClerkToken(req);
23
+ }
24
+ if (!user?.user_id) return res.status(401).json({ error: "Unauthorized" });
25
+
26
+ try {
27
+ const keys = await q.get_memory_keys_by_user.all(user.user_id);
28
+ res.json({ keys });
29
+ } catch (e: any) {
30
+ res.status(500).json({ error: e.message });
31
+ }
32
+ });
33
+
34
+ app.post("/keys", async (req: any, res: any) => {
35
+ let user = req.user;
36
+ if (!user?.user_id) {
37
+ user = await verifyClerkToken(req);
38
+ }
39
+ if (!user?.user_id) return res.status(401).json({ error: "Unauthorized" });
40
+
41
+ const { label } = req.body;
42
+ if (!label) return res.status(400).json({ error: "Label is required" });
43
+
44
+ try {
45
+ const id = crypto.randomUUID();
46
+ const secret_key = `opm_sk_${crypto.randomBytes(24).toString("hex")}`;
47
+ await q.ins_memory_key.run(id, user.user_id, label, secret_key, Date.now());
48
+ res.json({ id, label, secret_key });
49
+ } catch (e: any) {
50
+ res.status(500).json({ error: e.message });
51
+ }
52
+ });
53
+
54
+ app.patch("/keys/:id", async (req: any, res: any) => {
55
+ let user = req.user;
56
+ if (!user?.user_id) {
57
+ user = await verifyClerkToken(req);
58
+ }
59
+ if (!user?.user_id) return res.status(401).json({ error: "Unauthorized" });
60
+
61
+ const { id } = req.params;
62
+ const { label } = req.body;
63
+
64
+ // Validate label is non-empty (reject empty or whitespace-only labels)
65
+ if (!label || typeof label !== 'string' || label.trim().length === 0) {
66
+ return res.status(400).json({ error: "Label is required" });
67
+ }
68
+
69
+ try {
70
+ const updated_at = Date.now();
71
+ await q.upd_memory_key_label.run(label, updated_at, id, user.user_id);
72
+ res.json({ id, label, updated_at });
73
+ } catch (e: any) {
74
+ res.status(500).json({ error: e.message });
75
+ }
76
+ });
77
+
78
+ app.delete("/keys/:id", async (req: any, res: any) => {
79
+ let user = req.user;
80
+ if (!user?.user_id) {
81
+ user = await verifyClerkToken(req);
82
+ }
83
+ if (!user?.user_id) return res.status(401).json({ error: "Unauthorized" });
84
+
85
+ const { id } = req.params;
86
+ try {
87
+ await q.del_memory_key.run(id, user.user_id);
88
+ res.json({ ok: true });
89
+ } catch (e: any) {
90
+ res.status(500).json({ error: e.message });
91
+ }
92
+ });
93
+
94
+ // GET /keys/stats - Get workspace statistics with memory counts
95
+ app.get("/keys/stats", async (req: any, res: any) => {
96
+ let user = req.user;
97
+ if (!user?.user_id) {
98
+ user = await verifyClerkToken(req);
99
+ }
100
+ if (!user?.user_id) return res.status(401).json({ error: "Unauthorized" });
101
+
102
+ try {
103
+ // Get all workspaces for the user (already sorted by created_at desc)
104
+ const workspaces = await q.get_memory_keys_by_user.all(user.user_id);
105
+
106
+ // Get memory counts grouped by memory_key_id
107
+ const memoryCounts = await q.get_memory_counts_by_key.all(user.user_id);
108
+
109
+ // Create a map of memory_key_id -> count for efficient lookup
110
+ const countMap = new Map<string, number>();
111
+ for (const item of memoryCounts) {
112
+ if (item.memory_key_id) {
113
+ countMap.set(item.memory_key_id, item.count);
114
+ }
115
+ }
116
+
117
+ // Merge workspaces with memory counts (default to 0 if no memories)
118
+ const workspacesWithStats = workspaces.map((workspace: any) => ({
119
+ id: workspace.id,
120
+ label: workspace.label,
121
+ secret_key: workspace.secret_key,
122
+ created_at: workspace.created_at,
123
+ memory_count: countMap.get(workspace.id) || 0
124
+ }));
125
+
126
+ res.json({ workspaces: workspacesWithStats });
127
+ } catch (e: any) {
128
+ res.status(500).json({ error: e.message });
129
+ }
130
+ });
131
+ }
@@ -0,0 +1,71 @@
1
+ import {
2
+ store_node_mem,
3
+ retrieve_node_mems,
4
+ get_graph_ctx,
5
+ create_refl,
6
+ get_lg_cfg,
7
+ } from "../../ai/graph";
8
+ import type {
9
+ lgm_store_req,
10
+ lgm_retrieve_req,
11
+ lgm_context_req,
12
+ lgm_reflection_req,
13
+ } from "../../core/types";
14
+
15
+ export function lg(app: any) {
16
+ app.get("/lgm/config", (_req: any, res: any) => {
17
+ res.json(get_lg_cfg());
18
+ });
19
+
20
+ app.post("/lgm/store", async (req: any, res: any) => {
21
+ try {
22
+ const r = await store_node_mem(req.body as lgm_store_req);
23
+ res.json(r);
24
+ } catch (e) {
25
+ console.error("[LGM] store error:", e);
26
+ res.status(400).json({
27
+ err: "lgm_store_failed",
28
+ message: (e as Error).message,
29
+ });
30
+ }
31
+ });
32
+
33
+ app.post("/lgm/retrieve", async (req: any, res: any) => {
34
+ try {
35
+ const r = await retrieve_node_mems(req.body as lgm_retrieve_req);
36
+ res.json(r);
37
+ } catch (e) {
38
+ console.error("[LGM] retrieve error:", e);
39
+ res.status(400).json({
40
+ err: "lgm_retrieve_failed",
41
+ message: (e as Error).message,
42
+ });
43
+ }
44
+ });
45
+
46
+ app.post("/lgm/context", async (req: any, res: any) => {
47
+ try {
48
+ const r = await get_graph_ctx(req.body as lgm_context_req);
49
+ res.json(r);
50
+ } catch (e) {
51
+ console.error("[LGM] context error:", e);
52
+ res.status(400).json({
53
+ err: "lgm_context_failed",
54
+ message: (e as Error).message,
55
+ });
56
+ }
57
+ });
58
+
59
+ app.post("/lgm/reflection", async (req: any, res: any) => {
60
+ try {
61
+ const r = await create_refl(req.body as lgm_reflection_req);
62
+ res.json(r);
63
+ } catch (e) {
64
+ console.error("[LGM] reflection error:", e);
65
+ res.status(400).json({
66
+ err: "lgm_reflection_failed",
67
+ message: (e as Error).message,
68
+ });
69
+ }
70
+ });
71
+ }