@itz4blitz/agentful 0.1.0 → 0.1.5
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/.claude/agents/architect.md +283 -11
- package/.claude/agents/backend.md +282 -218
- package/.claude/agents/frontend.md +242 -319
- package/.claude/agents/orchestrator.md +27 -27
- package/.claude/agents/reviewer.md +1 -1
- package/.claude/agents/tester.md +375 -284
- package/.claude/commands/agentful-decide.md +104 -29
- package/.claude/commands/agentful-start.md +18 -16
- package/.claude/commands/agentful-status.md +28 -22
- package/.claude/commands/agentful-validate.md +42 -20
- package/.claude/commands/agentful.md +329 -0
- package/.claude/product/README.md +1 -1
- package/.claude/product/index.md +1 -1
- package/.claude/settings.json +4 -3
- package/.claude/skills/conversation/SKILL.md +1130 -0
- package/LICENSE +1 -1
- package/README.md +557 -222
- package/bin/cli.js +319 -36
- package/lib/agent-generator.js +685 -0
- package/lib/domain-detector.js +468 -0
- package/lib/domain-structure-generator.js +770 -0
- package/lib/index.js +40 -0
- package/lib/project-analyzer.js +701 -0
- package/lib/tech-stack-detector.js +1091 -0
- package/lib/template-engine.js +153 -0
- package/package.json +14 -5
- package/template/CLAUDE.md +62 -21
- package/template/PRODUCT.md +89 -1
|
@@ -0,0 +1,1130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: conversation
|
|
3
|
+
description: Natural language understanding, intent classification, context management, reference resolution, and conversation history analysis for agentful
|
|
4
|
+
model: sonnet
|
|
5
|
+
tools: Read, Write, Edit, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Conversation Skill
|
|
9
|
+
|
|
10
|
+
This skill provides natural language processing capabilities for understanding user intent, managing conversation context, resolving references, and maintaining conversation history.
|
|
11
|
+
|
|
12
|
+
## Core Functions
|
|
13
|
+
|
|
14
|
+
### 1. Intent Classification
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
/**
|
|
18
|
+
* Classify user intent with confidence score
|
|
19
|
+
* @param message - User's current message
|
|
20
|
+
* @param conversation_history - Recent conversation context
|
|
21
|
+
* @param product_spec - Product features and requirements
|
|
22
|
+
* @returns Intent classification with confidence
|
|
23
|
+
*/
|
|
24
|
+
function classify_intent(
|
|
25
|
+
message: string,
|
|
26
|
+
conversation_history: ConversationMessage[],
|
|
27
|
+
product_spec: ProductSpec
|
|
28
|
+
): IntentClassification {
|
|
29
|
+
const intents = [
|
|
30
|
+
'feature_request',
|
|
31
|
+
'bug_report',
|
|
32
|
+
'question',
|
|
33
|
+
'clarification',
|
|
34
|
+
'status_update',
|
|
35
|
+
'mind_change',
|
|
36
|
+
'context_switch',
|
|
37
|
+
'approval',
|
|
38
|
+
'rejection',
|
|
39
|
+
'pause',
|
|
40
|
+
'continue'
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Analyze message patterns
|
|
44
|
+
const patterns = {
|
|
45
|
+
feature_request: /(?:add|create|implement|build|new|feature|support|enable)/i,
|
|
46
|
+
bug_report: /(?:bug|broken|error|issue|problem|wrong|doesn't work|fix)/i,
|
|
47
|
+
question: /(?:how|what|where|when|why|who|which|can you|explain)/i,
|
|
48
|
+
clarification: /(?:what do you mean|clarify|elaborate|more detail)/i,
|
|
49
|
+
status_update: /(?:status|progress|where are we|what's left)/i,
|
|
50
|
+
mind_change: /(?:actually|wait|never mind|forget that|change|instead)/i,
|
|
51
|
+
context_switch: /(?:let's talk about|switching to|moving on|about)/i,
|
|
52
|
+
approval: /(?:yes|ok|sure|go ahead|approved|sounds good|let's do it)/i,
|
|
53
|
+
rejection: /(?:no|stop|don't|cancel|never mind)/i,
|
|
54
|
+
pause: /(?:pause|hold on|wait|brb|later)/i,
|
|
55
|
+
continue: /(?:continue|resume|let's continue|back)/i
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Score each intent
|
|
59
|
+
const scores = {};
|
|
60
|
+
for (const [intent, pattern] of Object.entries(patterns)) {
|
|
61
|
+
const matches = message.match(pattern);
|
|
62
|
+
const baseScore = matches ? matches.length * 0.3 : 0;
|
|
63
|
+
|
|
64
|
+
// Context-aware scoring
|
|
65
|
+
let contextBonus = 0;
|
|
66
|
+
if (conversation_history.length > 0) {
|
|
67
|
+
const lastMessage = conversation_history[conversation_history.length - 1];
|
|
68
|
+
if (lastMessage.role === 'assistant' && intent === 'clarification') {
|
|
69
|
+
contextBonus += 0.2; // User asking for clarification after assistant response
|
|
70
|
+
}
|
|
71
|
+
if (lastMessage.intent === 'question' && intent === 'clarification') {
|
|
72
|
+
contextBonus += 0.15; // Follow-up clarification
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
scores[intent] = Math.min(0.95, baseScore + contextBonus);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Find highest scoring intent
|
|
80
|
+
const topIntent = Object.entries(scores).reduce((a, b) =>
|
|
81
|
+
a[1] > b[1] ? a : b
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
intent: topIntent[0],
|
|
86
|
+
confidence: topIntent[1],
|
|
87
|
+
alternative_intents: Object.entries(scores)
|
|
88
|
+
.filter(([_, score]) => score > 0.3)
|
|
89
|
+
.sort((a, b) => b[1] - a[1])
|
|
90
|
+
.slice(1, 4)
|
|
91
|
+
.map(([intent, score]) => ({ intent, confidence: score }))
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface IntentClassification {
|
|
96
|
+
intent: string;
|
|
97
|
+
confidence: number;
|
|
98
|
+
alternative_intents: Array<{ intent: string; confidence: number }>;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Feature Extraction
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
/**
|
|
106
|
+
* Extract which feature/user is talking about
|
|
107
|
+
* @param message - User's current message
|
|
108
|
+
* @param product_spec - Product features and requirements
|
|
109
|
+
* @returns Extracted feature reference
|
|
110
|
+
*/
|
|
111
|
+
function extract_feature_mention(
|
|
112
|
+
message: string,
|
|
113
|
+
product_spec: ProductSpec
|
|
114
|
+
): FeatureMention {
|
|
115
|
+
const mentioned = [];
|
|
116
|
+
|
|
117
|
+
// Direct feature name matches
|
|
118
|
+
if (product_spec.features) {
|
|
119
|
+
for (const [featureId, feature] of Object.entries(product_spec.features)) {
|
|
120
|
+
const featureName = feature.name || featureId;
|
|
121
|
+
if (message.toLowerCase().includes(featureName.toLowerCase())) {
|
|
122
|
+
mentioned.push({
|
|
123
|
+
type: 'direct',
|
|
124
|
+
feature_id: featureId,
|
|
125
|
+
feature_name: featureName,
|
|
126
|
+
confidence: 0.9
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Hierarchical structure
|
|
133
|
+
if (product_spec.domains) {
|
|
134
|
+
for (const [domainId, domain] of Object.entries(product_spec.domains)) {
|
|
135
|
+
// Check domain mention
|
|
136
|
+
if (message.toLowerCase().includes(domain.name.toLowerCase())) {
|
|
137
|
+
mentioned.push({
|
|
138
|
+
type: 'domain',
|
|
139
|
+
domain_id: domainId,
|
|
140
|
+
domain_name: domain.name,
|
|
141
|
+
confidence: 0.85
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Check feature mentions within domain
|
|
146
|
+
if (domain.features) {
|
|
147
|
+
for (const [featureId, feature] of Object.entries(domain.features)) {
|
|
148
|
+
const featureName = feature.name || featureId;
|
|
149
|
+
if (message.toLowerCase().includes(featureName.toLowerCase())) {
|
|
150
|
+
mentioned.push({
|
|
151
|
+
type: 'feature',
|
|
152
|
+
domain_id: domainId,
|
|
153
|
+
feature_id: featureId,
|
|
154
|
+
feature_name: featureName,
|
|
155
|
+
confidence: 0.9
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Subtask mentions
|
|
164
|
+
const subtaskPattern = /(?:subtask|task|item)\s+(\d+)/i;
|
|
165
|
+
const subtaskMatch = message.match(subtaskPattern);
|
|
166
|
+
if (subtaskMatch) {
|
|
167
|
+
mentioned.push({
|
|
168
|
+
type: 'subtask_reference',
|
|
169
|
+
reference: subtaskMatch[1],
|
|
170
|
+
confidence: 0.7
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Return highest confidence mention or null
|
|
175
|
+
if (mentioned.length === 0) {
|
|
176
|
+
return { type: 'none', confidence: 0 };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return mentioned.sort((a, b) => b.confidence - a.confidence)[0];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface FeatureMention {
|
|
183
|
+
type: 'direct' | 'domain' | 'feature' | 'subtask_reference' | 'none';
|
|
184
|
+
domain_id?: string;
|
|
185
|
+
domain_name?: string;
|
|
186
|
+
feature_id?: string;
|
|
187
|
+
feature_name?: string;
|
|
188
|
+
reference?: string;
|
|
189
|
+
confidence: number;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 3. Bug Description Extraction
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
/**
|
|
197
|
+
* Extract bug details from conversation context
|
|
198
|
+
* @param message - User's current message
|
|
199
|
+
* @param conversation_history - Recent conversation context
|
|
200
|
+
* @returns Bug description with context
|
|
201
|
+
*/
|
|
202
|
+
function extract_bug_description(
|
|
203
|
+
message: string,
|
|
204
|
+
conversation_history: ConversationMessage[]
|
|
205
|
+
): BugDescription {
|
|
206
|
+
const bugInfo = {
|
|
207
|
+
description: message,
|
|
208
|
+
steps_to_reproduce: [],
|
|
209
|
+
expected_behavior: null,
|
|
210
|
+
actual_behavior: null,
|
|
211
|
+
related_feature: null,
|
|
212
|
+
severity: 'unknown',
|
|
213
|
+
context_messages: []
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Look for "expected" vs "actual" patterns
|
|
217
|
+
const expectedPattern = /(?:expected|should|supposed to):\s*(.+?)(?:\.|$)/i;
|
|
218
|
+
const actualPattern = /(?:actually|but it|instead):\s*(.+?)(?:\.|$)/i;
|
|
219
|
+
|
|
220
|
+
const expectedMatch = message.match(expectedPattern);
|
|
221
|
+
const actualMatch = message.match(actualPattern);
|
|
222
|
+
|
|
223
|
+
if (expectedMatch) {
|
|
224
|
+
bugInfo.expected_behavior = expectedMatch[1].trim();
|
|
225
|
+
}
|
|
226
|
+
if (actualMatch) {
|
|
227
|
+
bugInfo.actual_behavior = actualMatch[1].trim();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Look for numbered steps
|
|
231
|
+
const stepPattern = /^\d+\.\s*(.+)$/gm;
|
|
232
|
+
const steps = message.match(stepPattern);
|
|
233
|
+
if (steps) {
|
|
234
|
+
bugInfo.steps_to_reproduce = steps.map(step => step.replace(/^\d+\.\s*/, ''));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Infer severity from keywords
|
|
238
|
+
const severeKeywords = ['crash', 'broken', 'fail', 'critical', 'blocking'];
|
|
239
|
+
const minorKeywords = ['typo', 'cosmetic', 'minor', 'polish'];
|
|
240
|
+
|
|
241
|
+
if (severeKeywords.some(kw => message.toLowerCase().includes(kw))) {
|
|
242
|
+
bugInfo.severity = 'high';
|
|
243
|
+
} else if (minorKeywords.some(kw => message.toLowerCase().includes(kw))) {
|
|
244
|
+
bugInfo.severity = 'low';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Gather relevant context from conversation history
|
|
248
|
+
const relevantContext = conversation_history
|
|
249
|
+
.filter(msg => {
|
|
250
|
+
const msgTime = new Date(msg.timestamp);
|
|
251
|
+
const now = new Date();
|
|
252
|
+
const hoursDiff = (now.getTime() - msgTime.getTime()) / (1000 * 60 * 60);
|
|
253
|
+
return hoursDiff < 2; // Last 2 hours
|
|
254
|
+
})
|
|
255
|
+
.slice(-5); // Last 5 messages
|
|
256
|
+
|
|
257
|
+
bugInfo.context_messages = relevantContext;
|
|
258
|
+
|
|
259
|
+
return bugInfo;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
interface BugDescription {
|
|
263
|
+
description: string;
|
|
264
|
+
steps_to_reproduce: string[];
|
|
265
|
+
expected_behavior: string | null;
|
|
266
|
+
actual_behavior: string | null;
|
|
267
|
+
related_feature: string | null;
|
|
268
|
+
severity: 'high' | 'medium' | 'low' | 'unknown';
|
|
269
|
+
context_messages: ConversationMessage[];
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 4. Ambiguity Detection
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
/**
|
|
277
|
+
* Detect unclear or ambiguous requests
|
|
278
|
+
* @param message - User's current message
|
|
279
|
+
* @returns Ambiguity analysis
|
|
280
|
+
*/
|
|
281
|
+
function detect_ambiguity(message: string): AmbiguityAnalysis {
|
|
282
|
+
const ambiguities = [];
|
|
283
|
+
let confidence = 0;
|
|
284
|
+
|
|
285
|
+
// Check for pronouns without context
|
|
286
|
+
const pronounPattern = /\b(it|that|this|they|them|those)\b/gi;
|
|
287
|
+
const pronouns = message.match(pronounPattern);
|
|
288
|
+
if (pronouns && pronouns.length > 0) {
|
|
289
|
+
// If message starts with pronoun, highly likely needs context
|
|
290
|
+
if (/^(it|that|this|they|them)/i.test(message.trim())) {
|
|
291
|
+
ambiguities.push({
|
|
292
|
+
type: 'pronoun_without_antecedent',
|
|
293
|
+
severity: 'high',
|
|
294
|
+
text: pronouns[0],
|
|
295
|
+
message: 'Pronoun at start of message without clear referent'
|
|
296
|
+
});
|
|
297
|
+
confidence = Math.max(confidence, 0.8);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Check for vague verbs
|
|
302
|
+
const vagueVerbs = ['fix', 'update', 'change', 'improve', 'handle'];
|
|
303
|
+
const vagueVerbPattern = new RegExp(`\\b(${vagueVerbs.join('|')})\\b\\s+(?:it|that|this)`, 'i');
|
|
304
|
+
const vagueMatch = message.match(vagueVerbPattern);
|
|
305
|
+
if (vagueMatch) {
|
|
306
|
+
ambiguities.push({
|
|
307
|
+
type: 'vague_action',
|
|
308
|
+
severity: 'medium',
|
|
309
|
+
text: vagueMatch[0],
|
|
310
|
+
message: 'Action verb without specific target'
|
|
311
|
+
});
|
|
312
|
+
confidence = Math.max(confidence, 0.6);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Check for short messages (< 10 words)
|
|
316
|
+
const wordCount = message.split(/\s+/).length;
|
|
317
|
+
if (wordCount < 10 && wordCount > 1) {
|
|
318
|
+
ambiguities.push({
|
|
319
|
+
type: 'insufficient_detail',
|
|
320
|
+
severity: 'low',
|
|
321
|
+
text: message,
|
|
322
|
+
message: 'Message is very short, may lack detail'
|
|
323
|
+
});
|
|
324
|
+
confidence = Math.max(confidence, 0.4);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Check for multiple possible intents
|
|
328
|
+
const actionWords = message.split(/\s+/).filter(word =>
|
|
329
|
+
/^(add|create|fix|update|delete|remove|test|check|verify|deploy|build|run)/i.test(word)
|
|
330
|
+
);
|
|
331
|
+
if (actionWords.length > 2) {
|
|
332
|
+
ambiguities.push({
|
|
333
|
+
type: 'multiple_actions',
|
|
334
|
+
severity: 'medium',
|
|
335
|
+
text: actionWords.join(', '),
|
|
336
|
+
message: 'Multiple actions detected, unclear priority'
|
|
337
|
+
});
|
|
338
|
+
confidence = Math.max(confidence, 0.7);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
is_ambiguous: ambiguities.length > 0,
|
|
343
|
+
confidence,
|
|
344
|
+
ambiguities,
|
|
345
|
+
suggestion: ambiguities.length > 0 ? generate_clarification_suggestion(ambiguities) : null
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
interface AmbiguityAnalysis {
|
|
350
|
+
is_ambiguous: boolean;
|
|
351
|
+
confidence: number;
|
|
352
|
+
ambiguities: Array<{
|
|
353
|
+
type: string;
|
|
354
|
+
severity: 'high' | 'medium' | 'low';
|
|
355
|
+
text: string;
|
|
356
|
+
message: string;
|
|
357
|
+
}>;
|
|
358
|
+
suggestion: string | null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function generate_clarification_suggestion(ambiguities: any[]): string {
|
|
362
|
+
const highSeverity = ambiguities.find(a => a.severity === 'high');
|
|
363
|
+
if (highSeverity) {
|
|
364
|
+
return 'Could you please provide more specific details? What specifically are you referring to?';
|
|
365
|
+
}
|
|
366
|
+
return 'I want to make sure I understand correctly. Could you provide a bit more detail?';
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### 5. Clarification Suggestions
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
/**
|
|
374
|
+
* Generate helpful clarifying questions
|
|
375
|
+
* @param ambiguous_message - The ambiguous user message
|
|
376
|
+
* @param product_spec - Product features and requirements
|
|
377
|
+
* @returns Suggested clarifying questions
|
|
378
|
+
*/
|
|
379
|
+
function suggest_clarification(
|
|
380
|
+
ambiguous_message: string,
|
|
381
|
+
product_spec: ProductSpec
|
|
382
|
+
): ClarificationSuggestion {
|
|
383
|
+
const questions = [];
|
|
384
|
+
|
|
385
|
+
// Feature-specific clarification
|
|
386
|
+
const featureMention = extract_feature_mention(ambiguous_message, product_spec);
|
|
387
|
+
if (featureMention.type === 'none') {
|
|
388
|
+
// No feature mentioned, ask which one
|
|
389
|
+
if (product_spec.features) {
|
|
390
|
+
const featureNames = Object.values(product_spec.features)
|
|
391
|
+
.map(f => f.name || f.id)
|
|
392
|
+
.slice(0, 5);
|
|
393
|
+
questions.push({
|
|
394
|
+
type: 'feature_selection',
|
|
395
|
+
question: `Which feature are you referring to? For example: ${featureNames.join(', ')}`,
|
|
396
|
+
options: featureNames
|
|
397
|
+
});
|
|
398
|
+
} else if (product_spec.domains) {
|
|
399
|
+
const domainNames = Object.values(product_spec.domains)
|
|
400
|
+
.map(d => d.name)
|
|
401
|
+
.slice(0, 5);
|
|
402
|
+
questions.push({
|
|
403
|
+
type: 'domain_selection',
|
|
404
|
+
question: `Which domain would you like to work on? For example: ${domainNames.join(', ')}`,
|
|
405
|
+
options: domainNames
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Action-specific clarification
|
|
411
|
+
const actions = ambiguous_message.match(/(?:add|create|fix|update|delete|remove|test|check)/gi);
|
|
412
|
+
if (actions && actions.length > 1) {
|
|
413
|
+
questions.push({
|
|
414
|
+
type: 'action_priority',
|
|
415
|
+
question: `I see multiple actions: ${actions.join(', ')}. Which would you like me to focus on first?`,
|
|
416
|
+
options: actions
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Bug report clarification
|
|
421
|
+
if (/bug|broken|error|issue/i.test(ambiguous_message)) {
|
|
422
|
+
questions.push({
|
|
423
|
+
type: 'bug_details',
|
|
424
|
+
question: 'Could you provide more details about the bug? What should happen vs. what actually happens?',
|
|
425
|
+
options: null
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Generic clarification if no specific ones
|
|
430
|
+
if (questions.length === 0) {
|
|
431
|
+
questions.push({
|
|
432
|
+
type: 'generic',
|
|
433
|
+
question: 'Could you please provide more details about what you\'d like me to do?',
|
|
434
|
+
options: null
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
primary_question: questions[0].question,
|
|
440
|
+
follow_up_questions: questions.slice(1),
|
|
441
|
+
suggested_responses: questions[0].options || []
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
interface ClarificationSuggestion {
|
|
446
|
+
primary_question: string;
|
|
447
|
+
follow_up_questions: Array<{ type: string; question: string; options: string[] | null }>;
|
|
448
|
+
suggested_responses: string[];
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Conversation State Management
|
|
453
|
+
|
|
454
|
+
### State Structure
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
/**
|
|
458
|
+
* Conversation state schema
|
|
459
|
+
* Stored in: .agentful/conversation-state.json
|
|
460
|
+
*/
|
|
461
|
+
interface ConversationState {
|
|
462
|
+
// Current context
|
|
463
|
+
current_feature: {
|
|
464
|
+
domain_id?: string;
|
|
465
|
+
feature_id?: string;
|
|
466
|
+
feature_name?: string;
|
|
467
|
+
subtask_id?: string;
|
|
468
|
+
} | null;
|
|
469
|
+
|
|
470
|
+
current_phase: 'idle' | 'planning' | 'implementing' | 'testing' | 'reviewing' | 'deploying';
|
|
471
|
+
|
|
472
|
+
last_action: {
|
|
473
|
+
type: string;
|
|
474
|
+
description: string;
|
|
475
|
+
timestamp: string;
|
|
476
|
+
result?: any;
|
|
477
|
+
} | null;
|
|
478
|
+
|
|
479
|
+
// Related features context
|
|
480
|
+
related_features: Array<{
|
|
481
|
+
feature_id: string;
|
|
482
|
+
feature_name: string;
|
|
483
|
+
relationship: 'dependency' | 'similar' | 'related';
|
|
484
|
+
}>;
|
|
485
|
+
|
|
486
|
+
// User preferences
|
|
487
|
+
user_preferences: {
|
|
488
|
+
communication_style: 'concise' | 'detailed' | 'balanced';
|
|
489
|
+
update_frequency: 'immediate' | 'summary' | 'on_completion';
|
|
490
|
+
ask_before_deleting: boolean;
|
|
491
|
+
test_automatically: boolean;
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// Session tracking
|
|
495
|
+
session_start: string;
|
|
496
|
+
last_message_time: string;
|
|
497
|
+
message_count: number;
|
|
498
|
+
|
|
499
|
+
// Context health
|
|
500
|
+
context_health: {
|
|
501
|
+
is_stale: boolean;
|
|
502
|
+
last_confirmed_intent: string;
|
|
503
|
+
ambiguity_count: number;
|
|
504
|
+
clarification_count: number;
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Reference Resolution
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
/**
|
|
513
|
+
* Resolve pronouns and references to previous messages
|
|
514
|
+
* @param message - Current message with potential references
|
|
515
|
+
* @param conversation_history - Message history
|
|
516
|
+
* @param state - Current conversation state
|
|
517
|
+
* @returns Resolved message with references expanded
|
|
518
|
+
*/
|
|
519
|
+
function resolve_references(
|
|
520
|
+
message: string,
|
|
521
|
+
conversation_history: ConversationMessage[],
|
|
522
|
+
state: ConversationState
|
|
523
|
+
): ResolvedMessage {
|
|
524
|
+
let resolved = message;
|
|
525
|
+
const references = [];
|
|
526
|
+
|
|
527
|
+
// Replace "it", "that", "this" with actual referents
|
|
528
|
+
const pronounMap = {
|
|
529
|
+
'it': state.current_feature?.feature_name,
|
|
530
|
+
'that': state.last_action?.description,
|
|
531
|
+
'this': state.current_feature?.feature_name
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
for (const [pronoun, referent] of Object.entries(pronounMap)) {
|
|
535
|
+
if (referent) {
|
|
536
|
+
const pattern = new RegExp(`\\b${pronoun}\\b`, 'gi');
|
|
537
|
+
if (pattern.test(message)) {
|
|
538
|
+
resolved = resolved.replace(pattern, referent);
|
|
539
|
+
references.push({
|
|
540
|
+
original: pronoun,
|
|
541
|
+
resolved: referent,
|
|
542
|
+
type: 'pronoun'
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Resolve "the feature", "the bug", etc.
|
|
549
|
+
const definiteReferences = {
|
|
550
|
+
'the feature': state.current_feature?.feature_name,
|
|
551
|
+
'the bug': state.last_action?.type === 'bug_fix' ? state.last_action.description : null,
|
|
552
|
+
'the task': state.current_feature?.subtask_id
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
for (const [phrase, referent] of Object.entries(definiteReferences)) {
|
|
556
|
+
if (referent) {
|
|
557
|
+
resolved = resolved.replace(new RegExp(phrase, 'gi'), referent);
|
|
558
|
+
references.push({
|
|
559
|
+
original: phrase,
|
|
560
|
+
resolved: referent,
|
|
561
|
+
type: 'definite_reference'
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
original: message,
|
|
568
|
+
resolved,
|
|
569
|
+
references,
|
|
570
|
+
confidence: references.length > 0 ? 0.85 : 1.0
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
interface ResolvedMessage {
|
|
575
|
+
original: string;
|
|
576
|
+
resolved: string;
|
|
577
|
+
references: Array<{
|
|
578
|
+
original: string;
|
|
579
|
+
resolved: string;
|
|
580
|
+
type: string;
|
|
581
|
+
}>;
|
|
582
|
+
confidence: number;
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Context Loss Recovery
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
/**
|
|
590
|
+
* Detect and handle context loss (>24h gaps)
|
|
591
|
+
* @param conversation_history - Full conversation history
|
|
592
|
+
* @param state - Current conversation state
|
|
593
|
+
* @returns Context recovery recommendation
|
|
594
|
+
*/
|
|
595
|
+
function detect_context_loss(
|
|
596
|
+
conversation_history: ConversationMessage[],
|
|
597
|
+
state: ConversationState
|
|
598
|
+
): ContextRecovery {
|
|
599
|
+
const now = new Date();
|
|
600
|
+
const lastMessage = conversation_history[conversation_history.length - 1];
|
|
601
|
+
const lastMessageTime = new Date(lastMessage?.timestamp || state.session_start);
|
|
602
|
+
|
|
603
|
+
const hoursDiff = (now.getTime() - lastMessageTime.getTime()) / (1000 * 60 * 60);
|
|
604
|
+
|
|
605
|
+
if (hoursDiff > 24) {
|
|
606
|
+
// Context is stale
|
|
607
|
+
return {
|
|
608
|
+
is_stale: true,
|
|
609
|
+
hours_since_last_message: Math.round(hoursDiff),
|
|
610
|
+
recommendation: 'summarize_and_confirm',
|
|
611
|
+
message: `It's been ${Math.round(hoursDiff)} hours since our last conversation. Before I continue, let me confirm what we were working on.`,
|
|
612
|
+
context_summary: {
|
|
613
|
+
last_feature: state.current_feature?.feature_name,
|
|
614
|
+
last_phase: state.current_phase,
|
|
615
|
+
last_action: state.last_action?.description
|
|
616
|
+
},
|
|
617
|
+
suggested_confirmation: `We were working on ${state.current_feature?.feature_name || 'a feature'}. Would you like to continue with that, or would you prefer to start something new?`
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return {
|
|
622
|
+
is_stale: false,
|
|
623
|
+
hours_since_last_message: Math.round(hoursDiff),
|
|
624
|
+
recommendation: 'continue',
|
|
625
|
+
message: null
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
interface ContextRecovery {
|
|
630
|
+
is_stale: boolean;
|
|
631
|
+
hours_since_last_message: number;
|
|
632
|
+
recommendation: 'summarize_and_confirm' | 'continue' | 'reset';
|
|
633
|
+
message: string | null;
|
|
634
|
+
context_summary?: {
|
|
635
|
+
last_feature?: string;
|
|
636
|
+
last_phase?: string;
|
|
637
|
+
last_action?: string;
|
|
638
|
+
};
|
|
639
|
+
suggested_confirmation?: string;
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### User Preference Learning
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
/**
|
|
647
|
+
* Learn and adapt to user preferences
|
|
648
|
+
* @param conversation_history - Recent conversation history
|
|
649
|
+
* @param state - Current conversation state
|
|
650
|
+
* @returns Updated user preferences
|
|
651
|
+
*/
|
|
652
|
+
function learn_user_preferences(
|
|
653
|
+
conversation_history: ConversationMessage[],
|
|
654
|
+
state: ConversationState
|
|
655
|
+
): UserPreferences {
|
|
656
|
+
const preferences = { ...state.user_preferences };
|
|
657
|
+
|
|
658
|
+
// Analyze user's communication style
|
|
659
|
+
const userMessages = conversation_history.filter(m => m.role === 'user').slice(-20);
|
|
660
|
+
const avgMessageLength = userMessages.reduce((sum, m) =>
|
|
661
|
+
sum + m.content.split(/\s+/).length, 0
|
|
662
|
+
) / userMessages.length;
|
|
663
|
+
|
|
664
|
+
if (avgMessageLength < 10) {
|
|
665
|
+
preferences.communication_style = 'concise';
|
|
666
|
+
} else if (avgMessageLength > 30) {
|
|
667
|
+
preferences.communication_style = 'detailed';
|
|
668
|
+
} else {
|
|
669
|
+
preferences.communication_style = 'balanced';
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Detect preference for updates
|
|
673
|
+
const statusRequests = userMessages.filter(m =>
|
|
674
|
+
/status|progress|where are we|what's left/i.test(m.content)
|
|
675
|
+
).length;
|
|
676
|
+
|
|
677
|
+
if (statusRequests > 3) {
|
|
678
|
+
preferences.update_frequency = 'summary';
|
|
679
|
+
} else if (statusRequests === 0 && userMessages.length > 10) {
|
|
680
|
+
preferences.update_frequency = 'on_completion';
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Detect safety preference
|
|
684
|
+
const confirmationRequests = userMessages.filter(m =>
|
|
685
|
+
/are you sure|double check|verify|confirm/i.test(m.content)
|
|
686
|
+
).length;
|
|
687
|
+
|
|
688
|
+
preferences.ask_before_deleting = confirmationRequests > 2;
|
|
689
|
+
|
|
690
|
+
// Detect testing preference
|
|
691
|
+
const testMentions = userMessages.filter(m =>
|
|
692
|
+
/test|testing|tests|coverage/i.test(m.content)
|
|
693
|
+
).length;
|
|
694
|
+
|
|
695
|
+
preferences.test_automatically = testMentions > 2;
|
|
696
|
+
|
|
697
|
+
return preferences;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
interface UserPreferences {
|
|
701
|
+
communication_style: 'concise' | 'detailed' | 'balanced';
|
|
702
|
+
update_frequency: 'immediate' | 'summary' | 'on_completion';
|
|
703
|
+
ask_before_deleting: boolean;
|
|
704
|
+
test_automatically: boolean;
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## Conversation History Management
|
|
709
|
+
|
|
710
|
+
### History File Structure
|
|
711
|
+
|
|
712
|
+
```bash
|
|
713
|
+
# Stored in: .agentful/conversation-history.json
|
|
714
|
+
{
|
|
715
|
+
"version": "1.0",
|
|
716
|
+
"session_id": "uuid",
|
|
717
|
+
"started_at": "2026-01-18T00:00:00Z",
|
|
718
|
+
"messages": [
|
|
719
|
+
{
|
|
720
|
+
"id": "msg-uuid",
|
|
721
|
+
"role": "user|assistant|system",
|
|
722
|
+
"content": "Message text",
|
|
723
|
+
"timestamp": "2026-01-18T00:00:00Z",
|
|
724
|
+
"intent": "feature_request",
|
|
725
|
+
"entities": {
|
|
726
|
+
"feature_id": "login",
|
|
727
|
+
"domain_id": "authentication"
|
|
728
|
+
},
|
|
729
|
+
"references_resolved": ["it -> login feature"]
|
|
730
|
+
}
|
|
731
|
+
],
|
|
732
|
+
"context_snapshot": {
|
|
733
|
+
"current_feature": "login",
|
|
734
|
+
"current_phase": "implementing",
|
|
735
|
+
"related_features": ["register", "logout"]
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### History Operations
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
/**
|
|
744
|
+
* Add message to conversation history
|
|
745
|
+
*/
|
|
746
|
+
function add_message_to_history(
|
|
747
|
+
message: ConversationMessage,
|
|
748
|
+
history_path: string = '.agentful/conversation-history.json'
|
|
749
|
+
): void {
|
|
750
|
+
let history = read_conversation_history(history_path);
|
|
751
|
+
|
|
752
|
+
history.messages.push({
|
|
753
|
+
...message,
|
|
754
|
+
id: message.id || generate_uuid(),
|
|
755
|
+
timestamp: message.timestamp || new Date().toISOString()
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// Keep only last 100 messages to prevent file bloat
|
|
759
|
+
if (history.messages.length > 100) {
|
|
760
|
+
history.messages = history.messages.slice(-100);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
Write(history_path, JSON.stringify(history, null, 2));
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Read conversation history
|
|
768
|
+
*/
|
|
769
|
+
function read_conversation_history(
|
|
770
|
+
history_path: string = '.agentful/conversation-history.json'
|
|
771
|
+
): ConversationHistory {
|
|
772
|
+
try {
|
|
773
|
+
const content = Read(history_path);
|
|
774
|
+
return JSON.parse(content);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
// Initialize new history
|
|
777
|
+
return {
|
|
778
|
+
version: "1.0",
|
|
779
|
+
session_id: generate_uuid(),
|
|
780
|
+
started_at: new Date().toISOString(),
|
|
781
|
+
messages: [],
|
|
782
|
+
context_snapshot: null
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Get recent conversation context
|
|
789
|
+
*/
|
|
790
|
+
function get_recent_context(
|
|
791
|
+
history_path: string = '.agentful/conversation-history.json',
|
|
792
|
+
message_count: number = 10
|
|
793
|
+
): ConversationMessage[] {
|
|
794
|
+
const history = read_conversation_history(history_path);
|
|
795
|
+
return history.messages.slice(-message_count);
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
## Integration with Orchestrator
|
|
800
|
+
|
|
801
|
+
### Delegation Interface
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
/**
|
|
805
|
+
* Determine if delegation to another skill is needed
|
|
806
|
+
* @param intent - Classified intent from user message
|
|
807
|
+
* @param entities - Extracted entities (features, domains, etc.)
|
|
808
|
+
* @returns Delegation decision
|
|
809
|
+
*/
|
|
810
|
+
function determine_delegation(
|
|
811
|
+
intent: IntentClassification,
|
|
812
|
+
entities: FeatureMention
|
|
813
|
+
): DelegationDecision {
|
|
814
|
+
// Feature-related intents -> delegate to appropriate agent
|
|
815
|
+
if (['feature_request', 'bug_report', 'status_update'].includes(intent.intent)) {
|
|
816
|
+
if (entities.type === 'feature' || entities.type === 'direct') {
|
|
817
|
+
return {
|
|
818
|
+
should_delegate: true,
|
|
819
|
+
target_skill: determine_skill_for_feature(entities),
|
|
820
|
+
context: {
|
|
821
|
+
intent: intent.intent,
|
|
822
|
+
feature_id: entities.feature_id,
|
|
823
|
+
domain_id: entities.domain_id
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Validation request -> delegate to validation skill
|
|
830
|
+
if (/test|check|validate|verify/i.test(intent.intent)) {
|
|
831
|
+
return {
|
|
832
|
+
should_delegate: true,
|
|
833
|
+
target_skill: 'validation',
|
|
834
|
+
context: {
|
|
835
|
+
intent: intent.intent
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Product tracking -> delegate to product-tracking skill
|
|
841
|
+
if (['status_update', 'progress'].includes(intent.intent)) {
|
|
842
|
+
return {
|
|
843
|
+
should_delegate: true,
|
|
844
|
+
target_skill: 'product-tracking',
|
|
845
|
+
context: {
|
|
846
|
+
intent: intent.intent
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return {
|
|
852
|
+
should_delegate: false,
|
|
853
|
+
target_skill: null,
|
|
854
|
+
context: null
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
interface DelegationDecision {
|
|
859
|
+
should_delegate: boolean;
|
|
860
|
+
target_skill: string | null;
|
|
861
|
+
context: any;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function determine_skill_for_feature(entities: FeatureMention): string {
|
|
865
|
+
// Map domains/features to appropriate skills
|
|
866
|
+
const domainSkillMap = {
|
|
867
|
+
'authentication': 'backend',
|
|
868
|
+
'user-management': 'backend',
|
|
869
|
+
'database': 'backend',
|
|
870
|
+
'frontend': 'frontend',
|
|
871
|
+
'ui': 'frontend',
|
|
872
|
+
'testing': 'tester'
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
if (entities.domain_name) {
|
|
876
|
+
const domainKey = Object.keys(domainSkillMap).find(key =>
|
|
877
|
+
entities.domain_name.toLowerCase().includes(key)
|
|
878
|
+
);
|
|
879
|
+
if (domainKey) {
|
|
880
|
+
return domainSkillMap[domainKey];
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return 'backend'; // Default
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
### Completion Tracking Integration
|
|
889
|
+
|
|
890
|
+
```typescript
|
|
891
|
+
/**
|
|
892
|
+
* Update conversation state after action completion
|
|
893
|
+
* @param state - Current conversation state
|
|
894
|
+
* @param action_result - Result from delegated skill
|
|
895
|
+
* @returns Updated conversation state
|
|
896
|
+
*/
|
|
897
|
+
function update_conversation_state(
|
|
898
|
+
state: ConversationState,
|
|
899
|
+
action_result: ActionResult
|
|
900
|
+
): ConversationState {
|
|
901
|
+
const updated = { ...state };
|
|
902
|
+
|
|
903
|
+
// Update last action
|
|
904
|
+
updated.last_action = {
|
|
905
|
+
type: action_result.type,
|
|
906
|
+
description: action_result.description,
|
|
907
|
+
timestamp: new Date().toISOString(),
|
|
908
|
+
result: action_result
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
// Update phase based on action result
|
|
912
|
+
if (action_result.status === 'complete') {
|
|
913
|
+
if (state.current_phase === 'implementing') {
|
|
914
|
+
updated.current_phase = 'testing';
|
|
915
|
+
} else if (state.current_phase === 'testing') {
|
|
916
|
+
updated.current_phase = 'reviewing';
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Update message count and time
|
|
921
|
+
updated.message_count++;
|
|
922
|
+
updated.last_message_time = new Date().toISOString();
|
|
923
|
+
|
|
924
|
+
// Clear ambiguity count on successful action
|
|
925
|
+
if (action_result.status === 'success') {
|
|
926
|
+
updated.context_health.ambiguity_count = 0;
|
|
927
|
+
updated.context_health.clarification_count = 0;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return updated;
|
|
931
|
+
}
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
## Edge Case Handling
|
|
935
|
+
|
|
936
|
+
### Mind Changes
|
|
937
|
+
|
|
938
|
+
```typescript
|
|
939
|
+
/**
|
|
940
|
+
* Detect and handle user mind changes
|
|
941
|
+
* @param message - Current message
|
|
942
|
+
* @param state - Current conversation state
|
|
943
|
+
* @returns Mind change detection result
|
|
944
|
+
*/
|
|
945
|
+
function detect_mind_change(
|
|
946
|
+
message: string,
|
|
947
|
+
state: ConversationState
|
|
948
|
+
): MindChangeDetection {
|
|
949
|
+
const mindChangePatterns = [
|
|
950
|
+
/actually/i,
|
|
951
|
+
/wait/i,
|
|
952
|
+
/never mind/i,
|
|
953
|
+
/forget that/i,
|
|
954
|
+
/change\s+my\s+mind/i,
|
|
955
|
+
/instead/i,
|
|
956
|
+
/stop\s+(?:that|it)/i
|
|
957
|
+
];
|
|
958
|
+
|
|
959
|
+
const hasMindChange = mindChangePatterns.some(pattern => pattern.test(message));
|
|
960
|
+
|
|
961
|
+
if (hasMindChange) {
|
|
962
|
+
return {
|
|
963
|
+
detected: true,
|
|
964
|
+
confidence: 0.8,
|
|
965
|
+
previous_intent: state.last_action?.type,
|
|
966
|
+
suggestion: 'I understand you\'d like to change direction. What would you like to do instead?',
|
|
967
|
+
reset_context: /never mind|forget that|stop/i.test(message)
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
detected: false,
|
|
973
|
+
confidence: 0,
|
|
974
|
+
previous_intent: null,
|
|
975
|
+
suggestion: null,
|
|
976
|
+
reset_context: false
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
interface MindChangeDetection {
|
|
981
|
+
detected: boolean;
|
|
982
|
+
confidence: number;
|
|
983
|
+
previous_intent: string | null;
|
|
984
|
+
suggestion: string | null;
|
|
985
|
+
reset_context: boolean;
|
|
986
|
+
}
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
### Stale Context Handling
|
|
990
|
+
|
|
991
|
+
```typescript
|
|
992
|
+
/**
|
|
993
|
+
* Handle stale context when references no longer make sense
|
|
994
|
+
* @param message - Current message
|
|
995
|
+
* @param state - Current conversation state
|
|
996
|
+
* @returns Stale context handling response
|
|
997
|
+
*/
|
|
998
|
+
function handle_stale_context(
|
|
999
|
+
message: string,
|
|
1000
|
+
state: ConversationState
|
|
1001
|
+
): StaleContextResponse {
|
|
1002
|
+
// Check if current feature is referenced but stale
|
|
1003
|
+
const featureReference = extract_feature_mention(message, null);
|
|
1004
|
+
|
|
1005
|
+
if (featureReference.type === 'none' && state.current_feature) {
|
|
1006
|
+
// User said "it" but current feature is old
|
|
1007
|
+
const hoursSinceLastAction = state.last_action
|
|
1008
|
+
? (Date.now() - new Date(state.last_action.timestamp).getTime()) / (1000 * 60 * 60)
|
|
1009
|
+
: Infinity;
|
|
1010
|
+
|
|
1011
|
+
if (hoursSinceLastAction > 4) {
|
|
1012
|
+
return {
|
|
1013
|
+
is_stale: true,
|
|
1014
|
+
suggestion: `I'm not sure what you're referring to. We last worked on ${state.current_feature.feature_name} ${Math.round(hoursSinceLastAction)} hours ago. Are you still referring to that?`,
|
|
1015
|
+
confirm_needed: true
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
return {
|
|
1021
|
+
is_stale: false,
|
|
1022
|
+
suggestion: null,
|
|
1023
|
+
confirm_needed: false
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
interface StaleContextResponse {
|
|
1028
|
+
is_stale: boolean;
|
|
1029
|
+
suggestion: string | null;
|
|
1030
|
+
confirm_needed: boolean;
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
## Usage Example
|
|
1035
|
+
|
|
1036
|
+
```typescript
|
|
1037
|
+
// Complete conversation processing flow
|
|
1038
|
+
async function process_conversation(userMessage: string): Promise<ConversationResponse> {
|
|
1039
|
+
// 1. Load conversation state and history
|
|
1040
|
+
const state = load_conversation_state();
|
|
1041
|
+
const history = read_conversation_history();
|
|
1042
|
+
const productSpec = load_product_spec();
|
|
1043
|
+
|
|
1044
|
+
// 2. Check for context loss
|
|
1045
|
+
const contextRecovery = detect_context_loss(history.messages, state);
|
|
1046
|
+
if (contextRecovery.is_stale) {
|
|
1047
|
+
return {
|
|
1048
|
+
type: 'context_recovery',
|
|
1049
|
+
message: contextRecovery.message,
|
|
1050
|
+
confirmation_needed: true
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// 3. Detect mind changes
|
|
1055
|
+
const mindChange = detect_mind_change(userMessage, state);
|
|
1056
|
+
if (mindChange.detected && mindChange.reset_context) {
|
|
1057
|
+
state.current_feature = null;
|
|
1058
|
+
state.current_phase = 'idle';
|
|
1059
|
+
save_conversation_state(state);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// 4. Resolve references
|
|
1063
|
+
const resolved = resolve_references(userMessage, history.messages, state);
|
|
1064
|
+
|
|
1065
|
+
// 5. Classify intent
|
|
1066
|
+
const intent = classify_intent(resolved.resolved, history.messages, productSpec);
|
|
1067
|
+
|
|
1068
|
+
// 6. Extract entities
|
|
1069
|
+
const entities = extract_feature_mention(resolved.resolved, productSpec);
|
|
1070
|
+
|
|
1071
|
+
// 7. Detect ambiguity
|
|
1072
|
+
const ambiguity = detect_ambiguity(resolved.resolved);
|
|
1073
|
+
if (ambiguity.is_ambiguous && ambiguity.confidence > 0.6) {
|
|
1074
|
+
const clarification = suggest_clarification(resolved.resolved, productSpec);
|
|
1075
|
+
return {
|
|
1076
|
+
type: 'clarification_needed',
|
|
1077
|
+
message: clarification.primary_question,
|
|
1078
|
+
suggestions: clarification.suggested_responses
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// 8. Determine delegation
|
|
1083
|
+
const delegation = determine_delegation(intent, entities);
|
|
1084
|
+
|
|
1085
|
+
// 9. Add user message to history
|
|
1086
|
+
add_message_to_history({
|
|
1087
|
+
role: 'user',
|
|
1088
|
+
content: userMessage,
|
|
1089
|
+
intent: intent.intent,
|
|
1090
|
+
entities: entities,
|
|
1091
|
+
references_resolved: resolved.references
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
// 10. Return delegation decision or response
|
|
1095
|
+
if (delegation.should_delegate) {
|
|
1096
|
+
return {
|
|
1097
|
+
type: 'delegate',
|
|
1098
|
+
target_skill: delegation.target_skill,
|
|
1099
|
+
context: delegation.context
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
return {
|
|
1104
|
+
type: 'response',
|
|
1105
|
+
message: generate_response(intent, entities, state)
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
## File Locations
|
|
1111
|
+
|
|
1112
|
+
```
|
|
1113
|
+
.agentful/
|
|
1114
|
+
├── conversation-state.json # Current conversation state
|
|
1115
|
+
├── conversation-history.json # Full message history
|
|
1116
|
+
└── user-preferences.json # Learned user preferences (optional)
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
## Best Practices
|
|
1120
|
+
|
|
1121
|
+
1. **Always resolve references** before processing intent
|
|
1122
|
+
2. **Check for context loss** after long gaps (>24h)
|
|
1123
|
+
3. **Detect ambiguity early** and ask clarifying questions
|
|
1124
|
+
4. **Learn user preferences** over time
|
|
1125
|
+
5. **Track related features** for better context
|
|
1126
|
+
6. **Handle mind changes gracefully** without losing all context
|
|
1127
|
+
7. **Delegate appropriately** based on intent and entities
|
|
1128
|
+
8. **Keep history manageable** (last 100 messages)
|
|
1129
|
+
9. **Update state after every action**
|
|
1130
|
+
10. **Provide summaries** after context recovery
|