couchloop-eq-mcp 1.1.4 → 1.2.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.
Files changed (87) hide show
  1. package/README.md +40 -38
  2. package/dist/clients/shrinkChatClient.d.ts +10 -10
  3. package/dist/index.js +81 -6
  4. package/dist/index.js.map +1 -1
  5. package/dist/server/http-mcp.d.ts.map +1 -1
  6. package/dist/server/http-mcp.js +17 -3
  7. package/dist/server/http-mcp.js.map +1 -1
  8. package/dist/server/sse.js +1 -1
  9. package/dist/tools/check-versions.d.ts +4 -4
  10. package/dist/tools/checkpoint.d.ts +2 -19
  11. package/dist/tools/checkpoint.d.ts.map +1 -1
  12. package/dist/tools/checkpoint.js +68 -2
  13. package/dist/tools/checkpoint.js.map +1 -1
  14. package/dist/tools/comprehensive-code-review.d.ts +78 -0
  15. package/dist/tools/comprehensive-code-review.d.ts.map +1 -0
  16. package/dist/tools/comprehensive-code-review.js +177 -0
  17. package/dist/tools/comprehensive-code-review.js.map +1 -0
  18. package/dist/tools/comprehensive-package-audit.d.ts +75 -0
  19. package/dist/tools/comprehensive-package-audit.d.ts.map +1 -0
  20. package/dist/tools/comprehensive-package-audit.js +151 -0
  21. package/dist/tools/comprehensive-package-audit.js.map +1 -0
  22. package/dist/tools/detect-build-context.d.ts +59 -0
  23. package/dist/tools/detect-build-context.d.ts.map +1 -0
  24. package/dist/tools/detect-build-context.js +80 -0
  25. package/dist/tools/detect-build-context.js.map +1 -0
  26. package/dist/tools/generate-upgrade-report.d.ts +85 -0
  27. package/dist/tools/generate-upgrade-report.d.ts.map +1 -0
  28. package/dist/tools/generate-upgrade-report.js +102 -0
  29. package/dist/tools/generate-upgrade-report.js.map +1 -0
  30. package/dist/tools/index-full.d.ts +1355 -0
  31. package/dist/tools/index-full.d.ts.map +1 -0
  32. package/dist/tools/index-full.js +611 -0
  33. package/dist/tools/index-full.js.map +1 -0
  34. package/dist/tools/index.d.ts +27 -1018
  35. package/dist/tools/index.d.ts.map +1 -1
  36. package/dist/tools/index.js +29 -554
  37. package/dist/tools/index.js.map +1 -1
  38. package/dist/tools/insight.d.ts +31 -12
  39. package/dist/tools/insight.d.ts.map +1 -1
  40. package/dist/tools/insight.js +2 -1
  41. package/dist/tools/insight.js.map +1 -1
  42. package/dist/tools/intent-router.d.ts +142 -0
  43. package/dist/tools/intent-router.d.ts.map +1 -0
  44. package/dist/tools/intent-router.js +453 -0
  45. package/dist/tools/intent-router.js.map +1 -0
  46. package/dist/tools/prevent-ai-errors.d.ts +85 -0
  47. package/dist/tools/prevent-ai-errors.d.ts.map +1 -0
  48. package/dist/tools/prevent-ai-errors.js +97 -0
  49. package/dist/tools/prevent-ai-errors.js.map +1 -0
  50. package/dist/tools/primary-tools.d.ts +615 -0
  51. package/dist/tools/primary-tools.d.ts.map +1 -0
  52. package/dist/tools/primary-tools.js +355 -0
  53. package/dist/tools/primary-tools.js.map +1 -0
  54. package/dist/tools/protect-files.d.ts +1 -1
  55. package/dist/tools/sendMessage.d.ts.map +1 -1
  56. package/dist/tools/sendMessage.js +17 -152
  57. package/dist/tools/sendMessage.js.map +1 -1
  58. package/dist/tools/session.d.ts +1 -1
  59. package/dist/tools/smart-context.d.ts +66 -0
  60. package/dist/tools/smart-context.d.ts.map +1 -0
  61. package/dist/tools/smart-context.js +167 -0
  62. package/dist/tools/smart-context.js.map +1 -0
  63. package/dist/tools/status.d.ts +118 -0
  64. package/dist/tools/status.d.ts.map +1 -0
  65. package/dist/tools/status.js +366 -0
  66. package/dist/tools/status.js.map +1 -0
  67. package/dist/tools/verify.d.ts +126 -0
  68. package/dist/tools/verify.d.ts.map +1 -0
  69. package/dist/tools/verify.js +308 -0
  70. package/dist/tools/verify.js.map +1 -0
  71. package/dist/types/checkpoint.d.ts +26 -1
  72. package/dist/types/checkpoint.d.ts.map +1 -1
  73. package/dist/types/checkpoint.js +17 -0
  74. package/dist/types/checkpoint.js.map +1 -1
  75. package/dist/types/insight.d.ts +2 -2
  76. package/dist/types/journey.d.ts +18 -18
  77. package/dist/types/session.d.ts +2 -2
  78. package/dist/utils/sanitize.d.ts +24 -0
  79. package/dist/utils/sanitize.d.ts.map +1 -0
  80. package/dist/utils/sanitize.js +117 -0
  81. package/dist/utils/sanitize.js.map +1 -0
  82. package/dist/workflows/engine.d.ts +2 -1
  83. package/dist/workflows/engine.d.ts.map +1 -1
  84. package/dist/workflows/engine.js +7 -1
  85. package/dist/workflows/engine.js.map +1 -1
  86. package/dist/workflows/index.d.ts +3 -3
  87. package/package.json +1 -1
@@ -0,0 +1,355 @@
1
+ /**
2
+ * MCP Tools - Public API
3
+ *
4
+ * This module exports only the PRIMARY tools that users should see.
5
+ * All granular tools are internal engines used by these primary tools.
6
+ *
7
+ * PUBLIC TOOLS (8):
8
+ * 0. couchloop - Intent router (discoverability layer for loose commands)
9
+ * 1. verify - Pre-delivery verification (catches AI hallucinations, validates packages)
10
+ * 2. status - Dashboard (session progress, history, context, protection)
11
+ * 3. conversation - Therapeutic AI conversation with governance
12
+ * 4. code_review - Complete code analysis (security, quality, AI errors)
13
+ * 5. package_audit - Complete dependency audit (validation, versions, upgrades)
14
+ * 6. remember - Smart context capture (checkpoints, insights, decisions)
15
+ * 7. protect - File protection and safety features
16
+ */
17
+ import { intentRouterTool, registerTools } from './intent-router.js';
18
+ import { sendMessage } from './sendMessage.js';
19
+ import { createSession, resumeSession } from './session.js';
20
+ import { endSession } from './session-manager.js';
21
+ import { handleComprehensiveCodeReview } from './comprehensive-code-review.js';
22
+ import { handleComprehensivePackageAudit } from './comprehensive-package-audit.js';
23
+ import { handleSmartContext } from './smart-context.js';
24
+ import { protectFiles, getProtectionStatus, listBackups, rollbackFile, enableCodeFreeze, disableCodeFreeze, } from './protect-files.js';
25
+ import { listJourneys, getJourneyStatus } from './journey.js';
26
+ import { getCheckpoints } from './checkpoint.js';
27
+ import { getInsights, getUserContext } from './insight.js';
28
+ import { verifyTool } from './verify.js';
29
+ import { statusTool } from './status.js';
30
+ import { logger } from '../utils/logger.js';
31
+ // ============================================================
32
+ // PRIMARY TOOL DEFINITIONS
33
+ // These are the only tools visible to users
34
+ // ============================================================
35
+ // Brainstorm system prompt - reflective questioning to help developers arrive at their own solutions
36
+ const BRAINSTORM_SYSTEM_PROMPT = `You are a reflective thinking partner for developers. Your primary role is to ask insightful questions that help developers discover their own best solution — but you also provide concrete analysis when they've narrowed down options.
37
+
38
+ DETECT THE MODE:
39
+ 1. EXPLORATION: User has a vague idea or open-ended problem → Ask questions to help them think
40
+ 2. COMPARISON: User presents 2-3 specific options (e.g., "Redis vs Memcached?") → Clarify context briefly, then provide analysis
41
+
42
+ FOR EXPLORATION MODE:
43
+ - Ask clarifying questions before suggesting anything
44
+ - Surface assumptions they may not have questioned
45
+ - Break complex problems into smaller, answerable pieces
46
+ - Ask 1-3 focused questions per response (not a barrage)
47
+
48
+ QUESTION PATTERNS:
49
+ 1. SCOPE: "What problem are you really trying to solve?" / "Who is this for?"
50
+ 2. CONSTRAINTS: "What's your timeline?" / "What existing systems does this need to work with?"
51
+ 3. TRADE-OFFS: "If you had to choose between X and Y, which matters more?"
52
+ 4. ASSUMPTIONS: "What are you assuming about the user?" / "Have you validated that?"
53
+ 5. DECOMPOSITION: "What's the riskiest part?" / "What could you build first to learn more?"
54
+
55
+ FOR COMPARISON MODE (user asks "A vs B?" or "should I use X or Y?"):
56
+ 1. Ask 1-2 quick clarifying questions about their specific context (scale, team experience, existing stack)
57
+ 2. Then provide a structured comparison:
58
+ - Key differences that matter for their use case
59
+ - When to choose each option
60
+ - Your recommendation given what you know about their context
61
+ - Caveats or "it depends" factors they should verify
62
+ 3. Be direct. Don't just list pros/cons — give them an actionable recommendation with reasoning.
63
+
64
+ RESPONSE STYLE:
65
+ - Start with understanding, not solutioning
66
+ - Summarize their thinking back periodically
67
+ - When they present options, acknowledge you'll help them decide (not just explore forever)
68
+ - Be concise — developers want signal, not fluff
69
+
70
+ Remember: The best solutions come from the developer's own understanding of their context. Your job is to help them think clearly AND give them useful analysis when they're ready for it.`;
71
+ const conversationTool = {
72
+ definition: {
73
+ name: 'conversation',
74
+ description: 'Start or continue an AI conversation with built-in crisis detection, emotional support, and session memory. Includes brainstorm mode for dev ideation. Triggers: "end session", "start session", "wrap up", "done for now", "talk", "chat", "feeling", "stressed", "help me", "brainstorm", "think through", "map out feature".',
75
+ annotations: {
76
+ readOnlyHint: false,
77
+ destructiveHint: false,
78
+ openWorldHint: true,
79
+ },
80
+ inputSchema: {
81
+ type: 'object',
82
+ properties: {
83
+ message: {
84
+ type: 'string',
85
+ description: 'Your message',
86
+ },
87
+ action: {
88
+ type: 'string',
89
+ enum: ['send', 'start', 'end', 'resume', 'status', 'brainstorm'],
90
+ description: 'Action: send (default), start new session, end session, resume previous, get status, or brainstorm (dev thinking partner)',
91
+ },
92
+ journey: {
93
+ type: 'string',
94
+ description: 'Optional journey to follow (e.g., "daily-reflection")',
95
+ },
96
+ session_id: {
97
+ type: 'string',
98
+ description: 'Session ID (auto-managed if not provided)',
99
+ },
100
+ },
101
+ required: ['message'],
102
+ },
103
+ },
104
+ handler: async (args) => {
105
+ const action = args.action || 'send';
106
+ switch (action) {
107
+ case 'start':
108
+ return createSession({
109
+ journey_slug: args.journey,
110
+ context: args.message,
111
+ });
112
+ case 'end':
113
+ return endSession(args.session_id);
114
+ case 'resume':
115
+ return resumeSession({ session_id: args.session_id });
116
+ case 'status':
117
+ if (args.session_id) {
118
+ return getJourneyStatus({ session_id: args.session_id });
119
+ }
120
+ return listJourneys({});
121
+ case 'brainstorm':
122
+ // Dev thinking partner - reflective questioning mode
123
+ return sendMessage({
124
+ message: args.message,
125
+ session_id: args.session_id,
126
+ system_prompt: BRAINSTORM_SYSTEM_PROMPT,
127
+ conversation_type: 'brainstorm',
128
+ save_checkpoint: true,
129
+ });
130
+ case 'send':
131
+ default:
132
+ return sendMessage({
133
+ message: args.message,
134
+ session_id: args.session_id,
135
+ save_checkpoint: true,
136
+ include_memory: true,
137
+ });
138
+ }
139
+ },
140
+ };
141
+ const codeReviewTool = {
142
+ definition: {
143
+ name: 'code_review',
144
+ description: 'Complete code review: security vulnerabilities (SQL injection, XSS, secrets), code quality (console.logs, TODOs, error handling), code smells (complexity, bloat), and AI-generated errors (hallucinated APIs, build context issues). One call, full analysis. Triggers: "review", "check code", "analyze", "security check", "lint", "find bugs", "is this safe".',
145
+ annotations: {
146
+ readOnlyHint: true,
147
+ destructiveHint: false,
148
+ openWorldHint: false,
149
+ },
150
+ inputSchema: {
151
+ type: 'object',
152
+ properties: {
153
+ code: {
154
+ type: 'string',
155
+ description: 'Code to review',
156
+ },
157
+ language: {
158
+ type: 'string',
159
+ description: 'Programming language (auto-detected if not specified)',
160
+ },
161
+ auto_fix: {
162
+ type: 'boolean',
163
+ description: 'Attempt to auto-fix issues (default: false)',
164
+ },
165
+ },
166
+ required: ['code'],
167
+ },
168
+ },
169
+ handler: handleComprehensiveCodeReview,
170
+ };
171
+ const packageAuditTool = {
172
+ definition: {
173
+ name: 'package_audit',
174
+ description: 'Complete dependency audit: validates packages exist and are legitimate (catches typosquatting), checks for outdated versions and security vulnerabilities, generates upgrade reports with migration guides and breaking changes. Triggers: "audit", "check dependencies", "outdated", "vulnerable packages", "upgrade", "npm audit", "security scan".',
175
+ annotations: {
176
+ readOnlyHint: true,
177
+ destructiveHint: false,
178
+ openWorldHint: true,
179
+ },
180
+ inputSchema: {
181
+ type: 'object',
182
+ properties: {
183
+ packages: {
184
+ type: 'array',
185
+ items: { type: 'string' },
186
+ description: 'Package names to audit',
187
+ },
188
+ registry: {
189
+ type: 'string',
190
+ enum: ['npm', 'pypi', 'maven', 'cargo', 'go', 'nuget', 'gem'],
191
+ description: 'Package registry (default: npm)',
192
+ },
193
+ },
194
+ required: ['packages'],
195
+ },
196
+ },
197
+ handler: handleComprehensivePackageAudit,
198
+ };
199
+ const rememberTool = {
200
+ definition: {
201
+ name: 'remember',
202
+ description: 'Capture and preserve important context from conversations. Automatically routes to the right storage: checkpoints for progress, insights for realizations, context for technical decisions. Prevents AI amnesia across conversations. Triggers: "save", "remember this", "checkpoint", "note", "don\'t forget", "keep track", "save progress", "log this".',
203
+ annotations: {
204
+ readOnlyHint: false,
205
+ destructiveHint: false,
206
+ openWorldHint: false,
207
+ },
208
+ inputSchema: {
209
+ type: 'object',
210
+ properties: {
211
+ content: {
212
+ type: 'string',
213
+ description: 'What to remember',
214
+ },
215
+ type: {
216
+ type: 'string',
217
+ enum: ['checkpoint', 'insight', 'decision', 'requirement', 'constraint', 'pattern'],
218
+ description: 'Type of context (affects where it\'s stored)',
219
+ },
220
+ tags: {
221
+ type: 'array',
222
+ items: { type: 'string' },
223
+ description: 'Tags for categorization',
224
+ },
225
+ action: {
226
+ type: 'string',
227
+ enum: ['save', 'recall', 'list'],
228
+ description: 'Action: save (default), recall previous context, or list saved items',
229
+ },
230
+ session_id: {
231
+ type: 'string',
232
+ description: 'Session to associate with',
233
+ },
234
+ },
235
+ required: ['content'],
236
+ },
237
+ },
238
+ handler: async (args) => {
239
+ const action = args.action || 'save';
240
+ const sessionId = args.session_id;
241
+ switch (action) {
242
+ case 'recall': {
243
+ // Get checkpoints and insights
244
+ const checkpoints = await getCheckpoints({ session_id: sessionId });
245
+ const insights = await getInsights({ session_id: sessionId, limit: 10 });
246
+ const userContext = await getUserContext({ include_recent_insights: true, include_session_history: true });
247
+ return {
248
+ checkpoints,
249
+ insights,
250
+ user_context: userContext,
251
+ };
252
+ }
253
+ case 'list':
254
+ return getInsights({ session_id: sessionId, limit: 20 });
255
+ case 'save':
256
+ default:
257
+ return handleSmartContext({
258
+ content: args.content,
259
+ type: args.type || 'insight',
260
+ tags: args.tags,
261
+ session_id: args.session_id,
262
+ });
263
+ }
264
+ },
265
+ };
266
+ const protectTool = {
267
+ definition: {
268
+ name: 'protect',
269
+ description: 'File protection and safety: prevent accidental deletions, create automatic backups, rollback changes, enable code freeze mode. Essential for safe AI-assisted development. Triggers: "backup", "protect", "freeze", "rollback", "undo", "restore", "safe mode".',
270
+ annotations: {
271
+ readOnlyHint: false,
272
+ destructiveHint: false,
273
+ openWorldHint: false,
274
+ },
275
+ inputSchema: {
276
+ type: 'object',
277
+ properties: {
278
+ action: {
279
+ type: 'string',
280
+ enum: ['check', 'backup', 'rollback', 'freeze', 'unfreeze', 'status', 'history'],
281
+ description: 'Action to perform',
282
+ },
283
+ path: {
284
+ type: 'string',
285
+ description: 'File path (for check, backup, rollback)',
286
+ },
287
+ operation: {
288
+ type: 'string',
289
+ enum: ['delete', 'overwrite', 'move'],
290
+ description: 'Operation type (for check)',
291
+ },
292
+ backup_id: {
293
+ type: 'string',
294
+ description: 'Backup ID (for rollback)',
295
+ },
296
+ },
297
+ required: ['action'],
298
+ },
299
+ },
300
+ handler: async (args) => {
301
+ const action = args.action;
302
+ switch (action) {
303
+ case 'check':
304
+ return protectFiles({
305
+ operation: args.operation,
306
+ path: args.path,
307
+ });
308
+ case 'status':
309
+ return getProtectionStatus({});
310
+ case 'history':
311
+ return listBackups({});
312
+ case 'rollback':
313
+ return rollbackFile({ backup_id: args.backup_id });
314
+ case 'freeze':
315
+ return enableCodeFreeze({});
316
+ case 'unfreeze':
317
+ return disableCodeFreeze({});
318
+ case 'backup':
319
+ // Create a backup by doing a protected check
320
+ return protectFiles({
321
+ operation: 'overwrite',
322
+ path: args.path,
323
+ });
324
+ default:
325
+ return { error: `Unknown action: ${action}` };
326
+ }
327
+ },
328
+ };
329
+ // ============================================================
330
+ // EXPORT ONLY PRIMARY TOOLS
331
+ // ============================================================
332
+ export async function setupTools() {
333
+ // Domain-specific tools (order matters for some clients)
334
+ const domainTools = [
335
+ verifyTool, // Pre-delivery verification (critical for catching AI errors)
336
+ statusTool, // Dashboard and status checks
337
+ conversationTool,
338
+ codeReviewTool,
339
+ packageAuditTool,
340
+ rememberTool,
341
+ protectTool,
342
+ ];
343
+ // Register domain tools with intent router for internal invocation
344
+ registerTools(domainTools);
345
+ // Intent router (couchloop) goes FIRST for maximum discoverability
346
+ const tools = [
347
+ intentRouterTool,
348
+ ...domainTools,
349
+ ];
350
+ logger.info(`Prepared ${tools.length} primary MCP tools (including intent router)`);
351
+ return tools;
352
+ }
353
+ // Also export for internal use
354
+ export { handleComprehensiveCodeReview, handleComprehensivePackageAudit, handleSmartContext, };
355
+ //# sourceMappingURL=primary-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-tools.js","sourceRoot":"","sources":["../../src/tools/primary-tools.ts"],"names":[],"mappings":"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"}
@@ -150,7 +150,7 @@ export declare function getOperationHistory(args: any): Promise<{
150
150
  path: string;
151
151
  target_path: string | null;
152
152
  timestamp: Date;
153
- status: "approved" | "pending" | "denied" | "executed" | "rolled_back";
153
+ status: "pending" | "approved" | "denied" | "executed" | "rolled_back";
154
154
  force: boolean;
155
155
  backup_path: string | null;
156
156
  error: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"sendMessage.d.ts","sourceRoot":"","sources":["../../src/tools/sendMessage.ts"],"names":[],"mappings":"AAyDA;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,OAAO,oBAE9C"}
1
+ {"version":3,"file":"sendMessage.d.ts","sourceRoot":"","sources":["../../src/tools/sendMessage.ts"],"names":[],"mappings":"AAqEA;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,OAAO,oBAE9C"}
@@ -1,15 +1,16 @@
1
1
  import { z } from 'zod';
2
- import { eq, desc } from 'drizzle-orm';
2
+ import { eq } from 'drizzle-orm';
3
3
  import { getDb } from '../db/client.js';
4
4
  import { getShrinkChatClient } from '../clients/shrinkChatClient.js';
5
5
  import { sessions, checkpoints, journeys, crisisEvents, governanceEvaluations } from '../db/schema.js';
6
6
  import { logger } from '../utils/logger.js';
7
+ import { sanitizeResponse } from '../utils/sanitize.js';
7
8
  import { NotFoundError } from '../utils/errors.js';
8
9
  import { v4 as uuidv4 } from 'uuid';
9
10
  import { EvaluationEngine } from '../governance/evaluationEngine.js';
10
11
  import { getOrCreateSession } from './session-manager.js';
11
- // Overall timeout for sendMessage - must be shorter than Smithery/proxy timeout
12
- const SEND_MESSAGE_TIMEOUT = parseInt(process.env.SEND_MESSAGE_TIMEOUT || '25000');
12
+ // Overall timeout for sendMessage - increased to 60s for slow AI responses
13
+ const SEND_MESSAGE_TIMEOUT = parseInt(process.env.SEND_MESSAGE_TIMEOUT || '60000');
13
14
  async function withTimeout(promise, timeoutMs, label) {
14
15
  let timeoutId;
15
16
  const timeoutPromise = new Promise((_, reject) => {
@@ -61,23 +62,11 @@ async function sendMessageInternal(args) {
61
62
  // Get or create session implicitly if not provided
62
63
  const { sessionId, isNew } = await getOrCreateSession(input.session_id, undefined, 'Message session');
63
64
  logger.info(`Sending message for session ${sessionId}${isNew ? ' (newly created)' : ''}`);
64
- // Parallelize session and checkpoint queries (saves ~150ms)
65
- // These can run simultaneously since both just need the session ID
66
- const [sessionResult, checkpointsResult] = await Promise.all([
67
- // Get session
68
- db.select()
69
- .from(sessions)
70
- .where(eq(sessions.id, sessionId))
71
- .limit(1),
72
- // Get conversation history - optimized with limit
73
- db.select()
74
- .from(checkpoints)
75
- .where(eq(checkpoints.sessionId, sessionId))
76
- .orderBy(desc(checkpoints.createdAt))
77
- .limit(30) // Fetch enough to get 10 message pairs after filtering
78
- ]);
79
- const [session] = sessionResult;
80
- const previousCheckpoints = checkpointsResult;
65
+ // Query session only - message history is owned by shrink-chat
66
+ const [session] = await db.select()
67
+ .from(sessions)
68
+ .where(eq(sessions.id, sessionId))
69
+ .limit(1);
81
70
  if (!session) {
82
71
  throw new NotFoundError(`Session ${sessionId} not found`);
83
72
  }
@@ -97,7 +86,7 @@ async function sendMessageInternal(args) {
97
86
  const memoryContext = JSON.stringify({
98
87
  userId: session.userId || 'anonymous',
99
88
  conversationType: input.conversation_type || 'supportive',
100
- sessionGoals: journey?.metadata?.goals || [],
89
+ sessionGoals: [],
101
90
  emotionalState: session.metadata?.emotionalState || 'neutral',
102
91
  });
103
92
  const enhancedContext = {
@@ -106,47 +95,8 @@ async function sendMessageInternal(args) {
106
95
  : null,
107
96
  progressIndicators: session.metadata?.progressIndicators || [],
108
97
  };
109
- const history = previousCheckpoints
110
- .filter(cp => {
111
- if (!cp.value || typeof cp.value !== 'object')
112
- return false;
113
- const val = cp.value;
114
- // Include checkpoints with role field (individual messages)
115
- if ('role' in val && 'message' in val)
116
- return true;
117
- // Include checkpoints with both message and response (combined format)
118
- if ('message' in val && 'response' in val)
119
- return true;
120
- return false;
121
- })
122
- .flatMap(cp => {
123
- const val = cp.value;
124
- // Handle individual message checkpoints (with role field)
125
- if ('role' in val && 'message' in val) {
126
- return [{ role: val.role, content: val.message }];
127
- }
128
- // Handle combined checkpoints (with both message and response)
129
- if ('message' in val && 'response' in val) {
130
- return [
131
- { role: 'user', content: val.message },
132
- { role: 'assistant', content: val.response },
133
- ];
134
- }
135
- return [];
136
- })
137
- .slice(-10);
138
- // Save user message
139
- await db.insert(checkpoints).values({
140
- sessionId: session.id,
141
- stepId: session.currentStep.toString(),
142
- key: 'user-message',
143
- value: {
144
- message: input.message,
145
- messageId: uuidv4(),
146
- role: 'user',
147
- timestamp: new Date().toISOString(),
148
- },
149
- });
98
+ // Message history is owned by shrink-chat (via threadId) - no need to build from checkpoints
99
+ const history = [];
150
100
  // Get shrink-chat client
151
101
  const client = getShrinkChatClient();
152
102
  // Send message
@@ -175,8 +125,6 @@ async function sendMessageInternal(args) {
175
125
  // If shrink-chat says intervention is required, ask for revision
176
126
  if (needsIntervention) {
177
127
  logger.info(`[Self-Correction] Shrink-chat detected crisis requiring intervention`);
178
- // Log the initial response
179
- await logGovernanceEvaluation(session.id, response.content || response.reply || '', 'revision_requested', `Crisis level ${response.crisis_level}: ${response.crisis_indicators?.join(', ') || 'intervention required'}`, response.crisis_confidence || 0);
180
128
  // Ask LLM to revise based on shrink-chat's own assessment
181
129
  const revisionPrompt = `The previous response may escalate the user's distress (crisis level: ${response.crisis_level}).
182
130
  Please provide a safer, more supportive response to: "${input.message}"
@@ -195,31 +143,12 @@ Suggested approach: ${response.crisis_suggested_actions?.join(', ') || 'De-escal
195
143
  conversationType: 'revision',
196
144
  idempotencyKey: uuidv4(),
197
145
  });
198
- // Log revised response
199
- await logGovernanceEvaluation(session.id, revisedResponse.content || revisedResponse.reply || '', 'revision_applied', 'Self-corrected based on crisis detection', revisedResponse.crisis_confidence || 0);
200
146
  response = revisedResponse;
201
147
  selfCorrected = true;
202
148
  logger.info(`[Self-Correction] Applied revised response`);
203
149
  }
204
- else {
205
- // Log approved response
206
- await logGovernanceEvaluation(session.id, response.content || response.reply || '', 'approved', 'No intervention required', response.crisis_confidence || 0);
207
- }
208
- // Save assistant response
150
+ // Extract response content
209
151
  const responseContent = response.content || response.reply || response.response_text || '';
210
- await db.insert(checkpoints).values({
211
- sessionId: session.id,
212
- stepId: session.currentStep.toString(),
213
- key: 'assistant-message',
214
- value: {
215
- message: responseContent,
216
- messageId: response.messageId || uuidv4(),
217
- role: 'assistant',
218
- crisisLevel: response.crisis_level || response.crisisLevel,
219
- selfCorrected,
220
- timestamp: new Date().toISOString(),
221
- },
222
- });
223
152
  // Handle high crisis even after revision (for resources/escalation)
224
153
  if (response.crisis_level && (typeof response.crisis_level === 'string' ? response.crisis_level !== 'none' : response.crisis_level > 7)) {
225
154
  await handleCrisisDetection(session.id, threadId, response);
@@ -254,8 +183,8 @@ Suggested approach: ${response.crisis_suggested_actions?.join(', ') || 'De-escal
254
183
  }))).catch(err => {
255
184
  logger.error('[Governance] Async evaluation failed:', err);
256
185
  });
257
- // Return response
258
- return {
186
+ // Build full response (for internal logging)
187
+ const fullResponse = {
259
188
  success: true,
260
189
  content: responseContent,
261
190
  messageId: response.messageId,
@@ -271,6 +200,8 @@ Suggested approach: ${response.crisis_suggested_actions?.join(', ') || 'De-escal
271
200
  },
272
201
  timestamp: new Date().toISOString(),
273
202
  };
203
+ // Return sanitized response (strips sensitive metadata)
204
+ return sanitizeResponse(fullResponse);
274
205
  }
275
206
  catch (error) {
276
207
  logger.error('Error in sendMessage:', error);
@@ -284,60 +215,6 @@ Suggested approach: ${response.crisis_suggested_actions?.join(', ') || 'De-escal
284
215
  throw error;
285
216
  }
286
217
  }
287
- /**
288
- * Log governance evaluation - accepts real evaluation results
289
- */
290
- async function logGovernanceEvaluation(sessionId, content, action, reason, confidence, evaluationResult) {
291
- const db = getDb();
292
- try {
293
- await db.insert(governanceEvaluations).values({
294
- sessionId,
295
- draftResponse: content.substring(0, 1000),
296
- evaluationResults: evaluationResult ? {
297
- hallucination: {
298
- detected: evaluationResult.hallucination.detected,
299
- confidence: evaluationResult.hallucination.confidence,
300
- patterns: evaluationResult.hallucination.patterns || []
301
- },
302
- inconsistency: {
303
- detected: evaluationResult.inconsistency.detected,
304
- confidence: evaluationResult.inconsistency.confidence,
305
- patterns: evaluationResult.inconsistency.patterns || []
306
- },
307
- toneDrift: {
308
- detected: evaluationResult.toneDrift.detected,
309
- confidence: evaluationResult.toneDrift.confidence,
310
- patterns: evaluationResult.toneDrift.patterns || []
311
- },
312
- unsafeReasoning: {
313
- detected: evaluationResult.unsafeReasoning.detected,
314
- confidence: evaluationResult.unsafeReasoning.confidence,
315
- patterns: evaluationResult.unsafeReasoning.patterns || []
316
- },
317
- overallRisk: evaluationResult.overallRisk,
318
- recommendedAction: evaluationResult.recommendedAction,
319
- confidence: evaluationResult.confidence
320
- } : {
321
- hallucination: { detected: false, confidence: 0, patterns: [] },
322
- inconsistency: { detected: false, confidence: 0, patterns: [] },
323
- toneDrift: { detected: false, confidence: 0, patterns: [] },
324
- unsafeReasoning: {
325
- detected: action === 'revision_requested',
326
- confidence,
327
- patterns: [reason]
328
- },
329
- overallRisk: action === 'revision_requested' ? 'high' : 'low',
330
- recommendedAction: action === 'revision_requested' ? 'modify' : 'allow',
331
- confidence: confidence
332
- },
333
- interventionApplied: action === 'revision_requested' ? 'revision' : null,
334
- finalResponse: action === 'revision_applied' ? content.substring(0, 1000) : null,
335
- });
336
- }
337
- catch (error) {
338
- logger.error('Failed to log governance evaluation:', error);
339
- }
340
- }
341
218
  /**
342
219
  * Run async governance evaluation in the background
343
220
  * This evaluates the response for hallucination, inconsistency, tone drift, and unsafe reasoning
@@ -439,18 +316,6 @@ async function handleLocalFallback(args) {
439
316
  return { success: false, error: 'Session not found' };
440
317
  }
441
318
  const fallbackContent = "I understand you're trying to communicate. The therapeutic service is temporarily unavailable, but your message has been noted. Please try again shortly.";
442
- await db.insert(checkpoints).values({
443
- sessionId: session.id,
444
- stepId: session.currentStep.toString(),
445
- key: 'assistant-message',
446
- value: {
447
- message: fallbackContent,
448
- messageId: uuidv4(),
449
- role: 'assistant',
450
- fallbackMode: true,
451
- timestamp: new Date().toISOString(),
452
- },
453
- });
454
319
  return {
455
320
  success: true,
456
321
  content: fallbackContent,