@feelingmindful/thinking-graph 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -0
- package/dist/tools/learn.d.ts +4 -4
- package/dist/tools/research.d.ts +60 -0
- package/dist/tools/research.js +236 -0
- package/dist/tools/think.js +76 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { relateSchema, relateHandler } from './tools/relate.js';
|
|
|
9
9
|
import { recallSchema, recallHandler } from './tools/recall.js';
|
|
10
10
|
import { learnSchema, learnHandler } from './tools/learn.js';
|
|
11
11
|
import { exportSchema, exportHandler } from './tools/export.js';
|
|
12
|
+
import { researchSchema, researchHandler } from './tools/research.js';
|
|
12
13
|
// Legacy compat shim removed — use `think` tool directly
|
|
13
14
|
// ─── Storage setup ───────────────────────────────────────
|
|
14
15
|
const memoryOnly = process.env.THINKING_GRAPH_MEMORY_ONLY === 'true';
|
|
@@ -30,6 +31,7 @@ server.tool('relate', 'Create a typed, directional relationship between two node
|
|
|
30
31
|
server.tool('recall', 'Query the thinking graph — search by text, filter by type, traverse relationships, or search across projects.', recallSchema.shape, async (input) => recallHandler(graph, input));
|
|
31
32
|
server.tool('learn', 'Store durable knowledge — code facts, tech debt, insights, principles. Deduplicates similar content.', learnSchema.shape, async (input) => learnHandler(graph, input));
|
|
32
33
|
server.tool('export', 'Export the thinking graph as JSON or a human-readable markdown summary.', exportSchema.shape, async (input) => exportHandler(graph, input));
|
|
34
|
+
server.tool('research', 'Research a topic using Perplexity/Firecrawl, then ingest findings into the graph. Two-phase: call once to get an action plan, then again with findings to store them.', researchSchema.shape, async (input) => researchHandler(graph, input));
|
|
33
35
|
// ─── Startup ─────────────────────────────────────────────
|
|
34
36
|
async function main() {
|
|
35
37
|
await storage.initialize();
|
package/dist/tools/learn.d.ts
CHANGED
|
@@ -30,13 +30,13 @@ export declare const learnSchema: z.ZodObject<{
|
|
|
30
30
|
projectId?: string | undefined;
|
|
31
31
|
metadata?: Record<string, unknown> | undefined;
|
|
32
32
|
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
33
|
+
filePath?: string | undefined;
|
|
34
|
+
lineRange?: [number, number] | undefined;
|
|
33
35
|
relates?: {
|
|
34
36
|
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
35
37
|
targetId: string;
|
|
36
38
|
reasoning?: string | undefined;
|
|
37
39
|
}[] | undefined;
|
|
38
|
-
filePath?: string | undefined;
|
|
39
|
-
lineRange?: [number, number] | undefined;
|
|
40
40
|
effort?: string | undefined;
|
|
41
41
|
impact?: string | undefined;
|
|
42
42
|
violatedBy?: string[] | undefined;
|
|
@@ -46,13 +46,13 @@ export declare const learnSchema: z.ZodObject<{
|
|
|
46
46
|
projectId?: string | undefined;
|
|
47
47
|
metadata?: Record<string, unknown> | undefined;
|
|
48
48
|
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
49
|
+
filePath?: string | undefined;
|
|
50
|
+
lineRange?: [number, number] | undefined;
|
|
49
51
|
relates?: {
|
|
50
52
|
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
51
53
|
targetId: string;
|
|
52
54
|
reasoning?: string | undefined;
|
|
53
55
|
}[] | undefined;
|
|
54
|
-
filePath?: string | undefined;
|
|
55
|
-
lineRange?: [number, number] | undefined;
|
|
56
56
|
effort?: string | undefined;
|
|
57
57
|
impact?: string | undefined;
|
|
58
58
|
violatedBy?: string[] | undefined;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const researchSchema: z.ZodObject<{
|
|
4
|
+
query: z.ZodString;
|
|
5
|
+
intent: z.ZodDefault<z.ZodEnum<["fact_check", "explore", "compare", "how_to", "current_state"]>>;
|
|
6
|
+
context: z.ZodOptional<z.ZodString>;
|
|
7
|
+
researchId: z.ZodOptional<z.ZodString>;
|
|
8
|
+
findings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
9
|
+
content: z.ZodString;
|
|
10
|
+
source: z.ZodOptional<z.ZodString>;
|
|
11
|
+
confidence: z.ZodOptional<z.ZodEnum<["high", "medium", "low"]>>;
|
|
12
|
+
}, "strip", z.ZodTypeAny, {
|
|
13
|
+
content: string;
|
|
14
|
+
source?: string | undefined;
|
|
15
|
+
confidence?: "high" | "medium" | "low" | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
content: string;
|
|
18
|
+
source?: string | undefined;
|
|
19
|
+
confidence?: "high" | "medium" | "low" | undefined;
|
|
20
|
+
}>, "many">>;
|
|
21
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
22
|
+
scrapeUrls: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
23
|
+
recencyFilter: z.ZodOptional<z.ZodEnum<["hour", "day", "week", "month", "year"]>>;
|
|
24
|
+
domainFilter: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
25
|
+
}, "strip", z.ZodTypeAny, {
|
|
26
|
+
query: string;
|
|
27
|
+
intent: "fact_check" | "explore" | "compare" | "how_to" | "current_state";
|
|
28
|
+
projectId?: string | undefined;
|
|
29
|
+
context?: string | undefined;
|
|
30
|
+
researchId?: string | undefined;
|
|
31
|
+
findings?: {
|
|
32
|
+
content: string;
|
|
33
|
+
source?: string | undefined;
|
|
34
|
+
confidence?: "high" | "medium" | "low" | undefined;
|
|
35
|
+
}[] | undefined;
|
|
36
|
+
scrapeUrls?: string[] | undefined;
|
|
37
|
+
recencyFilter?: "hour" | "day" | "week" | "month" | "year" | undefined;
|
|
38
|
+
domainFilter?: string[] | undefined;
|
|
39
|
+
}, {
|
|
40
|
+
query: string;
|
|
41
|
+
projectId?: string | undefined;
|
|
42
|
+
intent?: "fact_check" | "explore" | "compare" | "how_to" | "current_state" | undefined;
|
|
43
|
+
context?: string | undefined;
|
|
44
|
+
researchId?: string | undefined;
|
|
45
|
+
findings?: {
|
|
46
|
+
content: string;
|
|
47
|
+
source?: string | undefined;
|
|
48
|
+
confidence?: "high" | "medium" | "low" | undefined;
|
|
49
|
+
}[] | undefined;
|
|
50
|
+
scrapeUrls?: string[] | undefined;
|
|
51
|
+
recencyFilter?: "hour" | "day" | "week" | "month" | "year" | undefined;
|
|
52
|
+
domainFilter?: string[] | undefined;
|
|
53
|
+
}>;
|
|
54
|
+
export type ResearchInput = z.infer<typeof researchSchema>;
|
|
55
|
+
export declare function researchHandler(graph: ThinkingGraph, input: ResearchInput): Promise<{
|
|
56
|
+
content: {
|
|
57
|
+
type: "text";
|
|
58
|
+
text: string;
|
|
59
|
+
}[];
|
|
60
|
+
}>;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const coerceBool = z.preprocess((v) => (v === 'true' ? true : v === 'false' ? false : v), z.boolean());
|
|
3
|
+
const RESEARCH_INTENTS = [
|
|
4
|
+
'fact_check', // Verify a claim or assumption
|
|
5
|
+
'explore', // Open-ended exploration of a topic
|
|
6
|
+
'compare', // Compare options, libraries, approaches
|
|
7
|
+
'how_to', // Find implementation guidance
|
|
8
|
+
'current_state', // Get current state of something (version, status, etc.)
|
|
9
|
+
];
|
|
10
|
+
const findingSchema = z.object({
|
|
11
|
+
content: z.string().describe('What was found'),
|
|
12
|
+
source: z.string().optional().describe('URL or citation'),
|
|
13
|
+
confidence: z.enum(['high', 'medium', 'low']).optional(),
|
|
14
|
+
});
|
|
15
|
+
export const researchSchema = z.object({
|
|
16
|
+
// Phase 1: initiate research
|
|
17
|
+
query: z.string().describe('What to research'),
|
|
18
|
+
intent: z.enum(RESEARCH_INTENTS).default('explore').describe('Research intent — guides tool selection'),
|
|
19
|
+
context: z.string().optional().describe('Why this research matters to the current task'),
|
|
20
|
+
// Phase 2: ingest findings (provide researchId from phase 1)
|
|
21
|
+
researchId: z.string().optional().describe('Node ID from phase 1 — triggers ingestion mode'),
|
|
22
|
+
findings: z.array(findingSchema).optional().describe('Results to store (phase 2)'),
|
|
23
|
+
// Options
|
|
24
|
+
projectId: z.string().optional(),
|
|
25
|
+
scrapeUrls: z.array(z.string()).optional().describe('Specific URLs to scrape with Firecrawl'),
|
|
26
|
+
recencyFilter: z.enum(['hour', 'day', 'week', 'month', 'year']).optional().describe('How recent results should be'),
|
|
27
|
+
domainFilter: z.array(z.string()).optional().describe('Restrict to these domains'),
|
|
28
|
+
});
|
|
29
|
+
function buildActionPlan(input) {
|
|
30
|
+
const steps = [];
|
|
31
|
+
const query = input.query;
|
|
32
|
+
// Choose Perplexity tool based on intent
|
|
33
|
+
switch (input.intent) {
|
|
34
|
+
case 'fact_check':
|
|
35
|
+
steps.push({
|
|
36
|
+
tool: 'mcp__perplexity__perplexity_ask',
|
|
37
|
+
description: 'Quick fact check with citations',
|
|
38
|
+
args: {
|
|
39
|
+
messages: [{ role: 'user', content: query }],
|
|
40
|
+
search_context_size: 'medium',
|
|
41
|
+
...(input.recencyFilter && { search_recency_filter: input.recencyFilter }),
|
|
42
|
+
...(input.domainFilter && { search_domain_filter: input.domainFilter }),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
break;
|
|
46
|
+
case 'compare':
|
|
47
|
+
steps.push({
|
|
48
|
+
tool: 'mcp__perplexity__perplexity_reason',
|
|
49
|
+
description: 'Step-by-step comparison with web grounding',
|
|
50
|
+
args: {
|
|
51
|
+
messages: [{ role: 'user', content: query }],
|
|
52
|
+
search_context_size: 'high',
|
|
53
|
+
...(input.recencyFilter && { search_recency_filter: input.recencyFilter }),
|
|
54
|
+
...(input.domainFilter && { search_domain_filter: input.domainFilter }),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
break;
|
|
58
|
+
case 'explore':
|
|
59
|
+
steps.push({
|
|
60
|
+
tool: 'mcp__perplexity__perplexity_research',
|
|
61
|
+
description: 'Deep multi-source research (30s+)',
|
|
62
|
+
args: {
|
|
63
|
+
messages: [{ role: 'user', content: query }],
|
|
64
|
+
reasoning_effort: 'medium',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
break;
|
|
68
|
+
case 'how_to':
|
|
69
|
+
steps.push({
|
|
70
|
+
tool: 'mcp__perplexity__perplexity_ask',
|
|
71
|
+
description: 'Find implementation guidance',
|
|
72
|
+
args: {
|
|
73
|
+
messages: [{ role: 'user', content: query }],
|
|
74
|
+
search_context_size: 'high',
|
|
75
|
+
...(input.recencyFilter && { search_recency_filter: input.recencyFilter }),
|
|
76
|
+
...(input.domainFilter && { search_domain_filter: input.domainFilter }),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case 'current_state':
|
|
81
|
+
steps.push({
|
|
82
|
+
tool: 'mcp__perplexity__perplexity_ask',
|
|
83
|
+
description: 'Get current state with recency filter',
|
|
84
|
+
args: {
|
|
85
|
+
messages: [{ role: 'user', content: query }],
|
|
86
|
+
search_recency_filter: input.recencyFilter ?? 'week',
|
|
87
|
+
search_context_size: 'medium',
|
|
88
|
+
...(input.domainFilter && { search_domain_filter: input.domainFilter }),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
// If specific URLs provided, add Firecrawl scrape steps
|
|
94
|
+
if (input.scrapeUrls?.length) {
|
|
95
|
+
for (const url of input.scrapeUrls) {
|
|
96
|
+
steps.push({
|
|
97
|
+
tool: 'mcp__firecrawl__firecrawl_scrape',
|
|
98
|
+
description: `Scrape ${url}`,
|
|
99
|
+
args: {
|
|
100
|
+
url,
|
|
101
|
+
formats: ['markdown'],
|
|
102
|
+
onlyMainContent: true,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// For explore/compare, also suggest a Firecrawl search for additional sources
|
|
108
|
+
if (input.intent === 'explore' || input.intent === 'compare') {
|
|
109
|
+
steps.push({
|
|
110
|
+
tool: 'mcp__firecrawl__firecrawl_search',
|
|
111
|
+
description: 'Search for additional sources',
|
|
112
|
+
args: {
|
|
113
|
+
query,
|
|
114
|
+
limit: 5,
|
|
115
|
+
sources: [{ type: 'web' }],
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return steps;
|
|
120
|
+
}
|
|
121
|
+
export async function researchHandler(graph, input) {
|
|
122
|
+
// ── Phase 2: ingest findings ──────────────────────────
|
|
123
|
+
if (input.researchId && input.findings?.length) {
|
|
124
|
+
const researchNode = await graph.getNode(input.researchId);
|
|
125
|
+
if (!researchNode) {
|
|
126
|
+
return {
|
|
127
|
+
content: [{
|
|
128
|
+
type: 'text',
|
|
129
|
+
text: JSON.stringify({ error: `Research node ${input.researchId} not found` }),
|
|
130
|
+
}],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const session = await graph.getCurrentSession();
|
|
134
|
+
const storedNodes = [];
|
|
135
|
+
for (const finding of input.findings) {
|
|
136
|
+
// Check for duplicates
|
|
137
|
+
const existing = await graph.findSimilar(finding.content, 'research', input.projectId);
|
|
138
|
+
if (existing) {
|
|
139
|
+
storedNodes.push(existing.id);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const node = await graph.addNode({
|
|
143
|
+
type: 'research',
|
|
144
|
+
content: finding.content,
|
|
145
|
+
sessionId: session.id,
|
|
146
|
+
projectId: input.projectId,
|
|
147
|
+
metadata: {
|
|
148
|
+
source: finding.source,
|
|
149
|
+
confidence: finding.confidence,
|
|
150
|
+
researchQuery: researchNode.content,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
// Relate finding back to the research query node
|
|
154
|
+
await graph.addEdge({
|
|
155
|
+
sourceId: node.id,
|
|
156
|
+
targetId: input.researchId,
|
|
157
|
+
type: 'supports',
|
|
158
|
+
reasoning: 'Finding from research query',
|
|
159
|
+
});
|
|
160
|
+
storedNodes.push(node.id);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
content: [{
|
|
164
|
+
type: 'text',
|
|
165
|
+
text: JSON.stringify({
|
|
166
|
+
phase: 'ingest',
|
|
167
|
+
researchId: input.researchId,
|
|
168
|
+
storedCount: storedNodes.length,
|
|
169
|
+
nodeIds: storedNodes,
|
|
170
|
+
suggestions: [
|
|
171
|
+
{
|
|
172
|
+
tool: 'relate',
|
|
173
|
+
when: 'Connect these findings to the thoughts/decisions they inform',
|
|
174
|
+
example: {
|
|
175
|
+
sourceId: '<thought nodeId>',
|
|
176
|
+
targetId: storedNodes[0] ?? '<finding nodeId>',
|
|
177
|
+
type: 'depends_on',
|
|
178
|
+
reasoning: 'Decision informed by research',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
tool: 'learn',
|
|
183
|
+
when: 'Promote a key finding to a durable insight that persists cross-project',
|
|
184
|
+
example: {
|
|
185
|
+
content: '<key takeaway>',
|
|
186
|
+
type: 'insight',
|
|
187
|
+
relates: [{ type: 'refines', targetId: input.researchId }],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
}),
|
|
192
|
+
}],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// ── Phase 1: initiate research ────────────────────────
|
|
196
|
+
const session = await graph.getCurrentSession();
|
|
197
|
+
const node = await graph.addNode({
|
|
198
|
+
type: 'research',
|
|
199
|
+
content: input.query,
|
|
200
|
+
sessionId: session.id,
|
|
201
|
+
projectId: input.projectId,
|
|
202
|
+
metadata: {
|
|
203
|
+
intent: input.intent,
|
|
204
|
+
context: input.context,
|
|
205
|
+
status: 'pending',
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
const actionPlan = buildActionPlan(input);
|
|
209
|
+
return {
|
|
210
|
+
content: [{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: JSON.stringify({
|
|
213
|
+
phase: 'initiate',
|
|
214
|
+
researchId: node.id,
|
|
215
|
+
query: input.query,
|
|
216
|
+
intent: input.intent,
|
|
217
|
+
actionPlan,
|
|
218
|
+
ingestStep: {
|
|
219
|
+
tool: 'research',
|
|
220
|
+
description: 'After executing the action plan, pipe findings back here',
|
|
221
|
+
args: {
|
|
222
|
+
query: input.query,
|
|
223
|
+
researchId: node.id,
|
|
224
|
+
findings: [
|
|
225
|
+
{
|
|
226
|
+
content: '<summarize what you found>',
|
|
227
|
+
source: '<url or citation>',
|
|
228
|
+
confidence: 'high',
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
}],
|
|
235
|
+
};
|
|
236
|
+
}
|
package/dist/tools/think.js
CHANGED
|
@@ -1,6 +1,79 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { NODE_TYPES, EDGE_TYPES } from '../engine/types.js';
|
|
2
|
+
import { NODE_TYPES, EDGE_TYPES, GLOBAL_NODE_TYPES } from '../engine/types.js';
|
|
3
3
|
const coerceBool = z.preprocess((v) => (v === 'true' ? true : v === 'false' ? false : v), z.boolean());
|
|
4
|
+
function buildSuggestions(type, thoughtNumber, relatedCount, stats) {
|
|
5
|
+
const suggestions = [];
|
|
6
|
+
// First thought in session — suggest recall to check prior knowledge
|
|
7
|
+
if (thoughtNumber === 1 && stats.totalNodes > 1) {
|
|
8
|
+
suggestions.push({
|
|
9
|
+
tool: 'recall',
|
|
10
|
+
when: 'Check what the graph already knows before reasoning from scratch',
|
|
11
|
+
example: { query: '<topic>', crossProject: true },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
// Decision nodes with no relationships — suggest relate
|
|
15
|
+
if (type === 'decision' && relatedCount === 0) {
|
|
16
|
+
suggestions.push({
|
|
17
|
+
tool: 'relate',
|
|
18
|
+
when: 'Connect this decision to what it depends on or what it supersedes',
|
|
19
|
+
example: { sourceId: '<this nodeId>', targetId: '?<search term>', type: 'depends_on' },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
// Assumption nodes — suggest relate and research to verify
|
|
23
|
+
if (type === 'assumption') {
|
|
24
|
+
suggestions.push({
|
|
25
|
+
tool: 'relate',
|
|
26
|
+
when: 'Link this assumption to the decision or design it supports',
|
|
27
|
+
example: { sourceId: '<this nodeId>', targetId: '?<decision>', type: 'supports' },
|
|
28
|
+
});
|
|
29
|
+
suggestions.push({
|
|
30
|
+
tool: 'research',
|
|
31
|
+
when: 'Verify this assumption with external sources before building on it',
|
|
32
|
+
example: { query: '<the assumption to verify>', intent: 'fact_check' },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// Detection or tech_debt — suggest relate to violated principle
|
|
36
|
+
if (type === 'detection' || type === 'tech_debt') {
|
|
37
|
+
suggestions.push({
|
|
38
|
+
tool: 'relate',
|
|
39
|
+
when: 'Link to the principle this violates (pre-seeded: SRP, DRY, YAGNI, etc.)',
|
|
40
|
+
example: { sourceId: '<this nodeId>', targetId: '?<principle name>', type: 'violates' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Insight, pattern, or principle — suggest learn to persist cross-project
|
|
44
|
+
if (GLOBAL_NODE_TYPES.includes(type)) {
|
|
45
|
+
suggestions.push({
|
|
46
|
+
tool: 'learn',
|
|
47
|
+
when: 'Persist this so it carries across sessions and projects',
|
|
48
|
+
example: { content: '<the insight>', type },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Code fact — suggest learn with file metadata
|
|
52
|
+
if (type === 'code_fact') {
|
|
53
|
+
suggestions.push({
|
|
54
|
+
tool: 'learn',
|
|
55
|
+
when: 'Store this code fact with its file location for future recall',
|
|
56
|
+
example: { content: '<fact>', type: 'code_fact', filePath: '<path>', lineRange: [0, 0] },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Research node via think — redirect to the full research tool
|
|
60
|
+
if (type === 'research') {
|
|
61
|
+
suggestions.push({
|
|
62
|
+
tool: 'research',
|
|
63
|
+
when: 'Use the research tool instead — it generates an action plan with Perplexity/Firecrawl calls and handles ingestion',
|
|
64
|
+
example: { query: '<what to research>', intent: 'explore' },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Generic: if the node has no relationships and there are other nodes, nudge relate
|
|
68
|
+
if (relatedCount === 0 && stats.totalNodes > 2 && type === 'thought') {
|
|
69
|
+
suggestions.push({
|
|
70
|
+
tool: 'relate',
|
|
71
|
+
when: 'Connect this thought to related nodes (use "?<search>" to find by content)',
|
|
72
|
+
example: { sourceId: '<this nodeId>', targetId: '?<search term>', type: 'supports' },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return suggestions;
|
|
76
|
+
}
|
|
4
77
|
export const thinkSchema = z.object({
|
|
5
78
|
thought: z.string().describe('The reasoning content'),
|
|
6
79
|
type: z.enum(NODE_TYPES).default('thought').describe('Node type'),
|
|
@@ -53,6 +126,7 @@ export async function thinkHandler(graph, input) {
|
|
|
53
126
|
}
|
|
54
127
|
}
|
|
55
128
|
const stats = await graph.storage.getStats();
|
|
129
|
+
const suggestions = buildSuggestions(input.type ?? 'thought', input.thoughtNumber, relatedCount, stats);
|
|
56
130
|
return {
|
|
57
131
|
content: [{
|
|
58
132
|
type: 'text',
|
|
@@ -64,6 +138,7 @@ export async function thinkHandler(graph, input) {
|
|
|
64
138
|
branches: [],
|
|
65
139
|
thoughtHistoryLength: stats.totalNodes,
|
|
66
140
|
relatedNodes: relatedCount,
|
|
141
|
+
...(suggestions.length > 0 && { suggestions }),
|
|
67
142
|
}),
|
|
68
143
|
}],
|
|
69
144
|
};
|
package/package.json
CHANGED