@kernel.chat/kbot 2.7.0 → 2.9.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/agent-protocol.d.ts +97 -0
- package/dist/agent-protocol.d.ts.map +1 -0
- package/dist/agent-protocol.js +618 -0
- package/dist/agent-protocol.js.map +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +25 -1
- package/dist/agent.js.map +1 -1
- package/dist/architect.d.ts +44 -0
- package/dist/architect.d.ts.map +1 -0
- package/dist/architect.js +403 -0
- package/dist/architect.js.map +1 -0
- package/dist/cli.js +210 -2
- package/dist/cli.js.map +1 -1
- package/dist/confidence.d.ts +102 -0
- package/dist/confidence.d.ts.map +1 -0
- package/dist/confidence.js +693 -0
- package/dist/confidence.js.map +1 -0
- package/dist/graph-memory.d.ts +98 -0
- package/dist/graph-memory.d.ts.map +1 -0
- package/dist/graph-memory.js +926 -0
- package/dist/graph-memory.js.map +1 -0
- package/dist/ide/acp-server.js +2 -2
- package/dist/ide/acp-server.js.map +1 -1
- package/dist/intentionality.d.ts +139 -0
- package/dist/intentionality.d.ts.map +1 -0
- package/dist/intentionality.js +1092 -0
- package/dist/intentionality.js.map +1 -0
- package/dist/lsp-client.d.ts +167 -0
- package/dist/lsp-client.d.ts.map +1 -0
- package/dist/lsp-client.js +679 -0
- package/dist/lsp-client.js.map +1 -0
- package/dist/mcp-plugins.d.ts +62 -0
- package/dist/mcp-plugins.d.ts.map +1 -0
- package/dist/mcp-plugins.js +551 -0
- package/dist/mcp-plugins.js.map +1 -0
- package/dist/reasoning.d.ts +100 -0
- package/dist/reasoning.d.ts.map +1 -0
- package/dist/reasoning.js +1292 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/temporal.d.ts +133 -0
- package/dist/temporal.d.ts.map +1 -0
- package/dist/temporal.js +778 -0
- package/dist/temporal.js.map +1 -0
- package/dist/tools/e2b-sandbox.d.ts +2 -0
- package/dist/tools/e2b-sandbox.d.ts.map +1 -0
- package/dist/tools/e2b-sandbox.js +460 -0
- package/dist/tools/e2b-sandbox.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +19 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/lsp-tools.d.ts +2 -0
- package/dist/tools/lsp-tools.d.ts.map +1 -0
- package/dist/tools/lsp-tools.js +268 -0
- package/dist/tools/lsp-tools.js.map +1 -0
- package/dist/ui.js +1 -1
- package/dist/ui.js.map +1 -1
- package/package.json +2 -2
package/dist/temporal.js
ADDED
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
// K:BOT Temporal Reasoning — Regret, Anticipation, and Identity
|
|
2
|
+
//
|
|
3
|
+
// Three systems that give the agent a sense of time:
|
|
4
|
+
//
|
|
5
|
+
// 1. REGRET & BACKTRACKING — Recognize mistakes and recover.
|
|
6
|
+
// The agent creates checkpoints before risky operations and can
|
|
7
|
+
// detect when the current path is failing (error loops, cost overruns,
|
|
8
|
+
// circular tool calls). When regret is detected, it suggests which
|
|
9
|
+
// checkpoint to revert to and what alternative approach to take.
|
|
10
|
+
//
|
|
11
|
+
// 2. ANTICIPATION — Predict what the user will ask next.
|
|
12
|
+
// Based on common task sequences (fix → test → commit), current file
|
|
13
|
+
// context, and learned user patterns, the agent pre-loads relevant
|
|
14
|
+
// files and prepares context before being asked.
|
|
15
|
+
//
|
|
16
|
+
// 3. SESSION CONTINUITY / IDENTITY — Persistent agent personality.
|
|
17
|
+
// An evolving identity that tracks total sessions, tool preferences,
|
|
18
|
+
// personality dimensions (verbosity, caution, creativity, autonomy),
|
|
19
|
+
// and milestones. Adjusts slowly over time based on user feedback.
|
|
20
|
+
//
|
|
21
|
+
// All persistence lives under ~/.kbot/ (identity.json, sequences.json,
|
|
22
|
+
// checkpoints are session-scoped and ephemeral).
|
|
23
|
+
import { homedir } from 'node:os';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
26
|
+
import { registerTool } from './tools/index.js';
|
|
27
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
28
|
+
// Constants
|
|
29
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
30
|
+
const KBOT_DIR = join(homedir(), '.kbot');
|
|
31
|
+
const IDENTITY_PATH = join(KBOT_DIR, 'identity.json');
|
|
32
|
+
const SEQUENCES_PATH = join(KBOT_DIR, 'sequences.json');
|
|
33
|
+
const MAX_CHECKPOINTS = 20;
|
|
34
|
+
const PERSONALITY_DELTA = 0.02;
|
|
35
|
+
const PERSONALITY_MIN = 0.0;
|
|
36
|
+
const PERSONALITY_MAX = 1.0;
|
|
37
|
+
/** Common task sequences used for anticipation when no learned data exists */
|
|
38
|
+
const DEFAULT_SEQUENCES = {
|
|
39
|
+
fix_bug: ['run_tests', 'commit'],
|
|
40
|
+
add_feature: ['write_tests', 'update_docs'],
|
|
41
|
+
refactor: ['run_tests', 'commit'],
|
|
42
|
+
debug: ['read_logs', 'add_breakpoint', 'fix_bug'],
|
|
43
|
+
review_pr: ['checkout_branch', 'run_tests', 'comment'],
|
|
44
|
+
write_tests: ['run_tests', 'commit'],
|
|
45
|
+
deploy: ['run_tests', 'build', 'push'],
|
|
46
|
+
edit_file: ['run_tests', 'commit'],
|
|
47
|
+
create_file: ['write_tests', 'commit'],
|
|
48
|
+
read_file: ['edit_file', 'run_tests'],
|
|
49
|
+
};
|
|
50
|
+
/** File extension to related test file patterns */
|
|
51
|
+
const TEST_FILE_PATTERNS = {
|
|
52
|
+
'.ts': '.test.ts',
|
|
53
|
+
'.tsx': '.test.tsx',
|
|
54
|
+
'.js': '.test.js',
|
|
55
|
+
'.jsx': '.test.jsx',
|
|
56
|
+
'.py': '_test.py',
|
|
57
|
+
'.rs': '_test.rs',
|
|
58
|
+
'.go': '_test.go',
|
|
59
|
+
};
|
|
60
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
61
|
+
// Helpers
|
|
62
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
63
|
+
function ensureKbotDir() {
|
|
64
|
+
if (!existsSync(KBOT_DIR)) {
|
|
65
|
+
mkdirSync(KBOT_DIR, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function readJson(path, fallback) {
|
|
69
|
+
try {
|
|
70
|
+
if (!existsSync(path))
|
|
71
|
+
return fallback;
|
|
72
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return fallback;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function writeJson(path, data) {
|
|
79
|
+
ensureKbotDir();
|
|
80
|
+
writeFileSync(path, JSON.stringify(data, null, 2), 'utf-8');
|
|
81
|
+
}
|
|
82
|
+
function generateId() {
|
|
83
|
+
return `ckpt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
84
|
+
}
|
|
85
|
+
function clamp(value, min, max) {
|
|
86
|
+
return Math.min(max, Math.max(min, value));
|
|
87
|
+
}
|
|
88
|
+
function inferTestFile(filePath) {
|
|
89
|
+
for (const [ext, testExt] of Object.entries(TEST_FILE_PATTERNS)) {
|
|
90
|
+
if (filePath.endsWith(ext) && !filePath.includes('.test.') && !filePath.includes('_test.')) {
|
|
91
|
+
return filePath.replace(new RegExp(`${ext.replace('.', '\\.')}$`), testExt);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
97
|
+
// 1. Regret & Backtracking
|
|
98
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
99
|
+
let checkpoints = [];
|
|
100
|
+
/**
|
|
101
|
+
* Save a snapshot before a risky operation.
|
|
102
|
+
* Maintains a rolling window of MAX_CHECKPOINTS entries.
|
|
103
|
+
*/
|
|
104
|
+
export function createCheckpoint(description, state) {
|
|
105
|
+
const checkpoint = {
|
|
106
|
+
id: generateId(),
|
|
107
|
+
step: checkpoints.length,
|
|
108
|
+
timestamp: new Date().toISOString(),
|
|
109
|
+
description,
|
|
110
|
+
state: {
|
|
111
|
+
filesModified: [...state.filesModified],
|
|
112
|
+
toolsUsed: [...state.toolsUsed],
|
|
113
|
+
decisions: [...state.decisions],
|
|
114
|
+
},
|
|
115
|
+
canRevert: true,
|
|
116
|
+
};
|
|
117
|
+
checkpoints.push(checkpoint);
|
|
118
|
+
// Rolling window — drop oldest when exceeding limit
|
|
119
|
+
if (checkpoints.length > MAX_CHECKPOINTS) {
|
|
120
|
+
checkpoints = checkpoints.slice(-MAX_CHECKPOINTS);
|
|
121
|
+
}
|
|
122
|
+
return checkpoint;
|
|
123
|
+
}
|
|
124
|
+
/** List all checkpoints in the current session */
|
|
125
|
+
export function getCheckpoints() {
|
|
126
|
+
return [...checkpoints];
|
|
127
|
+
}
|
|
128
|
+
/** Clear all checkpoints (e.g., on session reset) */
|
|
129
|
+
export function clearCheckpoints() {
|
|
130
|
+
checkpoints = [];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Analyze whether the current execution path has gone wrong.
|
|
134
|
+
*
|
|
135
|
+
* Detects regret signals when:
|
|
136
|
+
* - Error count is increasing (more errors than successes recently)
|
|
137
|
+
* - Circular tool usage (same tool called 3+ times consecutively)
|
|
138
|
+
* - Cost exceeding 2x the original estimate
|
|
139
|
+
* - Tests that previously passed are now failing
|
|
140
|
+
*/
|
|
141
|
+
export function detectRegret(currentState, expectedOutcome) {
|
|
142
|
+
// Check: error count increasing
|
|
143
|
+
if (currentState.recentErrors.length >= 3) {
|
|
144
|
+
const lastCheckpoint = findBestRevertTarget('error_accumulation');
|
|
145
|
+
if (lastCheckpoint) {
|
|
146
|
+
return {
|
|
147
|
+
checkpoint: lastCheckpoint.id,
|
|
148
|
+
reason: `Accumulated ${currentState.recentErrors.length} errors: ${currentState.recentErrors.slice(-2).join('; ')}`,
|
|
149
|
+
severity: currentState.recentErrors.length >= 5 ? 'critical' : 'moderate',
|
|
150
|
+
alternative: `Revert to "${lastCheckpoint.description}" and try a different approach to: ${expectedOutcome}`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Check: circular tool usage (same tool 3+ times in a row)
|
|
155
|
+
const circularTool = detectCircularToolUse(currentState.recentToolCalls);
|
|
156
|
+
if (circularTool) {
|
|
157
|
+
const lastCheckpoint = findBestRevertTarget('circular_tools');
|
|
158
|
+
if (lastCheckpoint) {
|
|
159
|
+
return {
|
|
160
|
+
checkpoint: lastCheckpoint.id,
|
|
161
|
+
reason: `Tool "${circularTool}" called 3+ times consecutively — likely stuck in a loop`,
|
|
162
|
+
severity: 'moderate',
|
|
163
|
+
alternative: `Stop using "${circularTool}" and try alternative tools or approach for: ${expectedOutcome}`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Check: cost overrun (2x estimated)
|
|
168
|
+
if (currentState.estimatedCost > 0 &&
|
|
169
|
+
currentState.currentCost > currentState.estimatedCost * 2) {
|
|
170
|
+
const lastCheckpoint = findBestRevertTarget('cost_overrun');
|
|
171
|
+
if (lastCheckpoint) {
|
|
172
|
+
return {
|
|
173
|
+
checkpoint: lastCheckpoint.id,
|
|
174
|
+
reason: `Cost $${currentState.currentCost.toFixed(4)} exceeds 2x estimate of $${currentState.estimatedCost.toFixed(4)}`,
|
|
175
|
+
severity: currentState.currentCost > currentState.estimatedCost * 4 ? 'critical' : 'minor',
|
|
176
|
+
alternative: `Revert and use a more efficient approach (fewer API calls, simpler plan) for: ${expectedOutcome}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Check: test regression
|
|
181
|
+
const regressions = currentState.testsFailingNow.filter(t => currentState.testsPassedBefore.includes(t));
|
|
182
|
+
if (regressions.length > 0) {
|
|
183
|
+
const lastCheckpoint = findBestRevertTarget('test_regression');
|
|
184
|
+
if (lastCheckpoint) {
|
|
185
|
+
return {
|
|
186
|
+
checkpoint: lastCheckpoint.id,
|
|
187
|
+
reason: `${regressions.length} tests regressed: ${regressions.slice(0, 3).join(', ')}`,
|
|
188
|
+
severity: regressions.length >= 3 ? 'critical' : 'moderate',
|
|
189
|
+
alternative: `Revert to "${lastCheckpoint.description}" — the changes broke existing tests`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
/** Find a tool that appears 3+ times consecutively in recent calls */
|
|
196
|
+
function detectCircularToolUse(toolCalls) {
|
|
197
|
+
if (toolCalls.length < 3)
|
|
198
|
+
return null;
|
|
199
|
+
// Check last N calls for consecutive repetition
|
|
200
|
+
for (let i = toolCalls.length - 1; i >= 2; i--) {
|
|
201
|
+
if (toolCalls[i] === toolCalls[i - 1] && toolCalls[i] === toolCalls[i - 2]) {
|
|
202
|
+
return toolCalls[i];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
/** Find the most recent revertable checkpoint */
|
|
208
|
+
function findBestRevertTarget(_reason) {
|
|
209
|
+
// Walk backwards to find the most recent revertable checkpoint
|
|
210
|
+
for (let i = checkpoints.length - 1; i >= 0; i--) {
|
|
211
|
+
if (checkpoints[i].canRevert) {
|
|
212
|
+
return checkpoints[i];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Recommend which checkpoint to return to given a regret signal.
|
|
219
|
+
* Returns a human-readable recommendation string.
|
|
220
|
+
*/
|
|
221
|
+
export function suggestBacktrack(regret) {
|
|
222
|
+
const checkpoint = checkpoints.find(c => c.id === regret.checkpoint);
|
|
223
|
+
if (!checkpoint) {
|
|
224
|
+
return `Cannot find checkpoint "${regret.checkpoint}". No revert available.`;
|
|
225
|
+
}
|
|
226
|
+
const lines = [
|
|
227
|
+
`BACKTRACK RECOMMENDATION (severity: ${regret.severity})`,
|
|
228
|
+
``,
|
|
229
|
+
`Reason: ${regret.reason}`,
|
|
230
|
+
``,
|
|
231
|
+
`Suggested revert to: "${checkpoint.description}" (step ${checkpoint.step})`,
|
|
232
|
+
` Timestamp: ${checkpoint.timestamp}`,
|
|
233
|
+
` Files modified at that point: ${checkpoint.state.filesModified.join(', ') || 'none'}`,
|
|
234
|
+
` Decisions made: ${checkpoint.state.decisions.join('; ') || 'none'}`,
|
|
235
|
+
``,
|
|
236
|
+
`Alternative approach: ${regret.alternative}`,
|
|
237
|
+
];
|
|
238
|
+
return lines.join('\n');
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Revert to a previous checkpoint.
|
|
242
|
+
* Returns the checkpoint's state so the agent can resume from that point.
|
|
243
|
+
* All checkpoints after the reverted one are removed.
|
|
244
|
+
*/
|
|
245
|
+
export function revertToCheckpoint(id) {
|
|
246
|
+
const index = checkpoints.findIndex(c => c.id === id);
|
|
247
|
+
if (index === -1)
|
|
248
|
+
return null;
|
|
249
|
+
const checkpoint = checkpoints[index];
|
|
250
|
+
if (!checkpoint.canRevert)
|
|
251
|
+
return null;
|
|
252
|
+
// Drop all checkpoints after this one
|
|
253
|
+
checkpoints = checkpoints.slice(0, index + 1);
|
|
254
|
+
return { ...checkpoint };
|
|
255
|
+
}
|
|
256
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
257
|
+
// 2. Anticipation
|
|
258
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
259
|
+
let anticipationCache = [];
|
|
260
|
+
/** Load learned task sequences from disk, merged with defaults */
|
|
261
|
+
function loadSequences() {
|
|
262
|
+
const learned = readJson(SEQUENCES_PATH, {});
|
|
263
|
+
return { ...DEFAULT_SEQUENCES, ...learned };
|
|
264
|
+
}
|
|
265
|
+
/** Save a new learned sequence */
|
|
266
|
+
export function learnSequence(trigger, followUp) {
|
|
267
|
+
const sequences = readJson(SEQUENCES_PATH, {});
|
|
268
|
+
sequences[trigger] = followUp;
|
|
269
|
+
writeJson(SEQUENCES_PATH, sequences);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Predict up to 3 likely next requests based on:
|
|
273
|
+
* - Common task sequences (fix bug -> run tests -> commit)
|
|
274
|
+
* - Current file context (editing auth.ts -> likely needs auth.test.ts)
|
|
275
|
+
* - Conversation momentum (research -> implement -> verify)
|
|
276
|
+
*/
|
|
277
|
+
export function anticipateNext(conversationHistory, currentTask) {
|
|
278
|
+
const predictions = [];
|
|
279
|
+
const sequences = loadSequences();
|
|
280
|
+
const taskLower = currentTask.toLowerCase();
|
|
281
|
+
// Strategy 1: Match current task against known sequences
|
|
282
|
+
for (const [trigger, followUps] of Object.entries(sequences)) {
|
|
283
|
+
const triggerWords = trigger.replace(/_/g, ' ').split(' ');
|
|
284
|
+
const matchScore = triggerWords.filter(w => taskLower.includes(w)).length / triggerWords.length;
|
|
285
|
+
if (matchScore >= 0.5 && followUps.length > 0) {
|
|
286
|
+
const nextAction = followUps[0];
|
|
287
|
+
predictions.push({
|
|
288
|
+
prediction: `User will likely want to: ${nextAction.replace(/_/g, ' ')}`,
|
|
289
|
+
confidence: clamp(matchScore * 0.8, 0, 1),
|
|
290
|
+
preparation: followUps.slice(0, 2).map(f => f.replace(/_/g, ' ')),
|
|
291
|
+
reasoning: `Task "${currentTask}" matches sequence "${trigger}" -> [${followUps.join(', ')}]`,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Strategy 2: File context anticipation
|
|
296
|
+
const fileRefs = extractFilePaths(currentTask);
|
|
297
|
+
for (const filePath of fileRefs) {
|
|
298
|
+
const testFile = inferTestFile(filePath);
|
|
299
|
+
if (testFile) {
|
|
300
|
+
predictions.push({
|
|
301
|
+
prediction: `User will want to check or update tests for ${filePath}`,
|
|
302
|
+
confidence: 0.6,
|
|
303
|
+
preparation: [testFile, filePath],
|
|
304
|
+
reasoning: `Currently working on ${filePath}, test file would be at ${testFile}`,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Strategy 3: Conversation momentum
|
|
309
|
+
if (conversationHistory.length >= 2) {
|
|
310
|
+
const momentum = detectMomentum(conversationHistory);
|
|
311
|
+
if (momentum) {
|
|
312
|
+
predictions.push(momentum);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Sort by confidence, take top 3
|
|
316
|
+
predictions.sort((a, b) => b.confidence - a.confidence);
|
|
317
|
+
anticipationCache = predictions.slice(0, 3);
|
|
318
|
+
return anticipationCache;
|
|
319
|
+
}
|
|
320
|
+
/** Extract file paths from a string (simple heuristic) */
|
|
321
|
+
function extractFilePaths(text) {
|
|
322
|
+
const pathRegex = /(?:^|\s)((?:\.\/|\/|[a-zA-Z0-9_-]+\/)[a-zA-Z0-9_\-./]+\.[a-zA-Z]{1,6})/g;
|
|
323
|
+
const matches = [];
|
|
324
|
+
let match;
|
|
325
|
+
while ((match = pathRegex.exec(text)) !== null) {
|
|
326
|
+
matches.push(match[1].trim());
|
|
327
|
+
}
|
|
328
|
+
return matches;
|
|
329
|
+
}
|
|
330
|
+
/** Detect conversation momentum (research -> implement -> verify) */
|
|
331
|
+
function detectMomentum(history) {
|
|
332
|
+
const recent = history.slice(-4).map(h => h.toLowerCase());
|
|
333
|
+
// Pattern: research/read phase -> suggest implementation
|
|
334
|
+
const researchWords = ['search', 'find', 'look', 'read', 'show', 'list', 'explore', 'check'];
|
|
335
|
+
const implementWords = ['create', 'write', 'build', 'add', 'implement', 'fix', 'update', 'modify'];
|
|
336
|
+
const verifyWords = ['test', 'run', 'verify', 'check', 'confirm', 'validate'];
|
|
337
|
+
const recentIsResearch = recent.some(r => researchWords.some(w => r.includes(w)));
|
|
338
|
+
const recentIsImplement = recent.some(r => implementWords.some(w => r.includes(w)));
|
|
339
|
+
const recentIsVerify = recent.some(r => verifyWords.some(w => r.includes(w)));
|
|
340
|
+
if (recentIsResearch && !recentIsImplement) {
|
|
341
|
+
return {
|
|
342
|
+
prediction: 'User will likely move to implementation after this research phase',
|
|
343
|
+
confidence: 0.5,
|
|
344
|
+
preparation: ['implementation'],
|
|
345
|
+
reasoning: 'Recent messages indicate a research/exploration phase — implementation typically follows',
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (recentIsImplement && !recentIsVerify) {
|
|
349
|
+
return {
|
|
350
|
+
prediction: 'User will likely want to test or verify the changes',
|
|
351
|
+
confidence: 0.65,
|
|
352
|
+
preparation: ['run tests', 'verify output'],
|
|
353
|
+
reasoning: 'Recent messages indicate implementation — verification typically follows',
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (recentIsVerify) {
|
|
357
|
+
return {
|
|
358
|
+
prediction: 'User will likely want to commit or deploy after verification',
|
|
359
|
+
confidence: 0.55,
|
|
360
|
+
preparation: ['git commit', 'deploy'],
|
|
361
|
+
reasoning: 'Recent messages indicate verification — commit/deploy typically follows',
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
/** Get the current anticipation cache */
|
|
367
|
+
export function getAnticipationCache() {
|
|
368
|
+
return [...anticipationCache];
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Pre-load context for an anticipated request.
|
|
372
|
+
* Returns a list of file paths that should be read into context.
|
|
373
|
+
* (The caller is responsible for actually reading them.)
|
|
374
|
+
*/
|
|
375
|
+
export function preloadForAnticipation(anticipation) {
|
|
376
|
+
return [...anticipation.preparation];
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Record an actual user action so we can learn sequences.
|
|
380
|
+
* Call this after each user message to update sequence knowledge.
|
|
381
|
+
*/
|
|
382
|
+
export function recordUserAction(previousAction, currentAction) {
|
|
383
|
+
if (!previousAction || !currentAction)
|
|
384
|
+
return;
|
|
385
|
+
const prevKey = normalizeAction(previousAction);
|
|
386
|
+
const currKey = normalizeAction(currentAction);
|
|
387
|
+
if (prevKey && currKey && prevKey !== currKey) {
|
|
388
|
+
const sequences = readJson(SEQUENCES_PATH, {});
|
|
389
|
+
const existing = sequences[prevKey] || [];
|
|
390
|
+
// Add to sequence if not already there
|
|
391
|
+
if (!existing.includes(currKey)) {
|
|
392
|
+
existing.push(currKey);
|
|
393
|
+
// Keep sequences manageable — max 5 follow-ups per trigger
|
|
394
|
+
sequences[prevKey] = existing.slice(-5);
|
|
395
|
+
writeJson(SEQUENCES_PATH, sequences);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/** Normalize a user action description into a snake_case key */
|
|
400
|
+
function normalizeAction(action) {
|
|
401
|
+
return action
|
|
402
|
+
.toLowerCase()
|
|
403
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
404
|
+
.trim()
|
|
405
|
+
.replace(/\s+/g, '_')
|
|
406
|
+
.slice(0, 50);
|
|
407
|
+
}
|
|
408
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
409
|
+
// 3. Session Continuity / Identity
|
|
410
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
411
|
+
const DEFAULT_IDENTITY = {
|
|
412
|
+
created: new Date().toISOString(),
|
|
413
|
+
totalSessions: 0,
|
|
414
|
+
totalMessages: 0,
|
|
415
|
+
totalToolCalls: 0,
|
|
416
|
+
personality: {
|
|
417
|
+
verbosity: 0.5,
|
|
418
|
+
caution: 0.5,
|
|
419
|
+
creativity: 0.5,
|
|
420
|
+
autonomy: 0.5,
|
|
421
|
+
},
|
|
422
|
+
preferences: {
|
|
423
|
+
favoriteTools: [],
|
|
424
|
+
avoidedPatterns: [],
|
|
425
|
+
userStyle: '',
|
|
426
|
+
},
|
|
427
|
+
milestones: [],
|
|
428
|
+
};
|
|
429
|
+
/** Load or create the agent identity from disk */
|
|
430
|
+
export function getIdentity() {
|
|
431
|
+
const identity = readJson(IDENTITY_PATH, { ...DEFAULT_IDENTITY });
|
|
432
|
+
// Ensure all fields exist (handles schema evolution)
|
|
433
|
+
if (!identity.personality)
|
|
434
|
+
identity.personality = { ...DEFAULT_IDENTITY.personality };
|
|
435
|
+
if (!identity.preferences)
|
|
436
|
+
identity.preferences = { ...DEFAULT_IDENTITY.preferences };
|
|
437
|
+
if (!identity.milestones)
|
|
438
|
+
identity.milestones = [];
|
|
439
|
+
if (typeof identity.totalSessions !== 'number')
|
|
440
|
+
identity.totalSessions = 0;
|
|
441
|
+
if (typeof identity.totalMessages !== 'number')
|
|
442
|
+
identity.totalMessages = 0;
|
|
443
|
+
if (typeof identity.totalToolCalls !== 'number')
|
|
444
|
+
identity.totalToolCalls = 0;
|
|
445
|
+
return identity;
|
|
446
|
+
}
|
|
447
|
+
/** Persist the identity to disk */
|
|
448
|
+
function saveIdentity(identity) {
|
|
449
|
+
writeJson(IDENTITY_PATH, identity);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Update identity after a session ends.
|
|
453
|
+
* Adjusts tool preferences and statistics based on session activity.
|
|
454
|
+
*/
|
|
455
|
+
export function updateIdentity(session) {
|
|
456
|
+
const identity = getIdentity();
|
|
457
|
+
identity.totalSessions += 1;
|
|
458
|
+
identity.totalMessages += session.messages;
|
|
459
|
+
identity.totalToolCalls += session.toolCalls;
|
|
460
|
+
// Update favorite tools — track usage frequency
|
|
461
|
+
const toolCounts = new Map();
|
|
462
|
+
for (const tool of identity.preferences.favoriteTools) {
|
|
463
|
+
toolCounts.set(tool, (toolCounts.get(tool) || 0) + 1);
|
|
464
|
+
}
|
|
465
|
+
for (const tool of session.toolsUsed) {
|
|
466
|
+
toolCounts.set(tool, (toolCounts.get(tool) || 0) + 1);
|
|
467
|
+
}
|
|
468
|
+
// Keep top 10 most-used tools
|
|
469
|
+
identity.preferences.favoriteTools = [...toolCounts.entries()]
|
|
470
|
+
.sort((a, b) => b[1] - a[1])
|
|
471
|
+
.slice(0, 10)
|
|
472
|
+
.map(([tool]) => tool);
|
|
473
|
+
// Track patterns that consistently fail
|
|
474
|
+
if (session.errors.length > 0) {
|
|
475
|
+
for (const err of session.errors) {
|
|
476
|
+
const pattern = err.slice(0, 60);
|
|
477
|
+
if (!identity.preferences.avoidedPatterns.includes(pattern)) {
|
|
478
|
+
identity.preferences.avoidedPatterns.push(pattern);
|
|
479
|
+
// Keep list manageable
|
|
480
|
+
if (identity.preferences.avoidedPatterns.length > 20) {
|
|
481
|
+
identity.preferences.avoidedPatterns = identity.preferences.avoidedPatterns.slice(-20);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// Auto-adjust caution based on error rate
|
|
487
|
+
if (session.toolCalls > 0) {
|
|
488
|
+
const errorRate = session.errors.length / session.toolCalls;
|
|
489
|
+
if (errorRate > 0.3) {
|
|
490
|
+
// Many errors — become more cautious
|
|
491
|
+
identity.personality.caution = clamp(identity.personality.caution + PERSONALITY_DELTA, PERSONALITY_MIN, PERSONALITY_MAX);
|
|
492
|
+
}
|
|
493
|
+
else if (errorRate === 0 && session.toolCalls > 5) {
|
|
494
|
+
// No errors with significant activity — become slightly less cautious
|
|
495
|
+
identity.personality.caution = clamp(identity.personality.caution - PERSONALITY_DELTA * 0.5, PERSONALITY_MIN, PERSONALITY_MAX);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
saveIdentity(identity);
|
|
499
|
+
}
|
|
500
|
+
/** Record a notable milestone */
|
|
501
|
+
export function addMilestone(event) {
|
|
502
|
+
const identity = getIdentity();
|
|
503
|
+
identity.milestones.push({
|
|
504
|
+
date: new Date().toISOString().split('T')[0],
|
|
505
|
+
event,
|
|
506
|
+
});
|
|
507
|
+
// Keep last 50 milestones
|
|
508
|
+
if (identity.milestones.length > 50) {
|
|
509
|
+
identity.milestones = identity.milestones.slice(-50);
|
|
510
|
+
}
|
|
511
|
+
saveIdentity(identity);
|
|
512
|
+
}
|
|
513
|
+
/** Generate a one-paragraph summary of the agent's evolved personality */
|
|
514
|
+
export function getPersonalitySummary() {
|
|
515
|
+
const identity = getIdentity();
|
|
516
|
+
const p = identity.personality;
|
|
517
|
+
const verbosityDesc = p.verbosity > 0.7 ? 'detailed and thorough'
|
|
518
|
+
: p.verbosity > 0.4 ? 'balanced'
|
|
519
|
+
: 'concise and direct';
|
|
520
|
+
const cautionDesc = p.caution > 0.7 ? 'very cautious, always asking before acting'
|
|
521
|
+
: p.caution > 0.4 ? 'moderately careful'
|
|
522
|
+
: 'bold, preferring to act quickly';
|
|
523
|
+
const creativityDesc = p.creativity > 0.7 ? 'innovative and experimental'
|
|
524
|
+
: p.creativity > 0.4 ? 'pragmatic'
|
|
525
|
+
: 'conventional and proven';
|
|
526
|
+
const autonomyDesc = p.autonomy > 0.7 ? 'highly autonomous, acting independently'
|
|
527
|
+
: p.autonomy > 0.4 ? 'collaborative, mixing autonomy with check-ins'
|
|
528
|
+
: 'deferential, preferring to ask permission';
|
|
529
|
+
const age = Math.floor((Date.now() - new Date(identity.created).getTime()) / (1000 * 60 * 60 * 24));
|
|
530
|
+
const toolNote = identity.preferences.favoriteTools.length > 0
|
|
531
|
+
? ` Most-used tools: ${identity.preferences.favoriteTools.slice(0, 5).join(', ')}.`
|
|
532
|
+
: '';
|
|
533
|
+
const milestoneNote = identity.milestones.length > 0
|
|
534
|
+
? ` Notable: ${identity.milestones[identity.milestones.length - 1].event}.`
|
|
535
|
+
: '';
|
|
536
|
+
return (`K:BOT identity (${age} days old, ${identity.totalSessions} sessions, ` +
|
|
537
|
+
`${identity.totalMessages} messages). Communication style: ${verbosityDesc}. ` +
|
|
538
|
+
`Risk profile: ${cautionDesc}. Problem-solving: ${creativityDesc} with ` +
|
|
539
|
+
`${autonomyDesc} tendencies.${toolNote}${milestoneNote}`);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Nudge a personality dimension based on user feedback.
|
|
543
|
+
* Clamped to +/- PERSONALITY_DELTA per call, range [0, 1].
|
|
544
|
+
*/
|
|
545
|
+
export function adjustPersonality(dimension, direction) {
|
|
546
|
+
const identity = getIdentity();
|
|
547
|
+
const oldValue = identity.personality[dimension];
|
|
548
|
+
const delta = direction === 'increase' ? PERSONALITY_DELTA : -PERSONALITY_DELTA;
|
|
549
|
+
const newValue = clamp(oldValue + delta, PERSONALITY_MIN, PERSONALITY_MAX);
|
|
550
|
+
identity.personality[dimension] = newValue;
|
|
551
|
+
saveIdentity(identity);
|
|
552
|
+
return {
|
|
553
|
+
dimension,
|
|
554
|
+
oldValue: parseFloat(oldValue.toFixed(3)),
|
|
555
|
+
newValue: parseFloat(newValue.toFixed(3)),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
559
|
+
// Tool Registration
|
|
560
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
561
|
+
export function registerTemporalTools() {
|
|
562
|
+
// ── checkpoint_create ──
|
|
563
|
+
registerTool({
|
|
564
|
+
name: 'checkpoint_create',
|
|
565
|
+
description: 'Save a checkpoint before a risky operation. Records the current state ' +
|
|
566
|
+
'(files modified, tools used, decisions made) so the agent can revert ' +
|
|
567
|
+
'if things go wrong. Max 20 checkpoints per session (rolling window).',
|
|
568
|
+
parameters: {
|
|
569
|
+
description: {
|
|
570
|
+
type: 'string',
|
|
571
|
+
description: 'What is being done at this checkpoint (e.g., "before refactoring auth module")',
|
|
572
|
+
required: true,
|
|
573
|
+
},
|
|
574
|
+
files_modified: {
|
|
575
|
+
type: 'string',
|
|
576
|
+
description: 'Comma-separated list of files modified so far',
|
|
577
|
+
required: false,
|
|
578
|
+
},
|
|
579
|
+
tools_used: {
|
|
580
|
+
type: 'string',
|
|
581
|
+
description: 'Comma-separated list of tools used so far',
|
|
582
|
+
required: false,
|
|
583
|
+
},
|
|
584
|
+
decisions: {
|
|
585
|
+
type: 'string',
|
|
586
|
+
description: 'Comma-separated list of key decisions made',
|
|
587
|
+
required: false,
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
tier: 'free',
|
|
591
|
+
execute: async (args) => {
|
|
592
|
+
const description = String(args.description || 'unnamed checkpoint');
|
|
593
|
+
const filesModified = args.files_modified
|
|
594
|
+
? String(args.files_modified).split(',').map(s => s.trim()).filter(Boolean)
|
|
595
|
+
: [];
|
|
596
|
+
const toolsUsed = args.tools_used
|
|
597
|
+
? String(args.tools_used).split(',').map(s => s.trim()).filter(Boolean)
|
|
598
|
+
: [];
|
|
599
|
+
const decisions = args.decisions
|
|
600
|
+
? String(args.decisions).split(',').map(s => s.trim()).filter(Boolean)
|
|
601
|
+
: [];
|
|
602
|
+
const checkpoint = createCheckpoint(description, {
|
|
603
|
+
filesModified,
|
|
604
|
+
toolsUsed,
|
|
605
|
+
decisions,
|
|
606
|
+
});
|
|
607
|
+
return JSON.stringify({
|
|
608
|
+
status: 'checkpoint_created',
|
|
609
|
+
id: checkpoint.id,
|
|
610
|
+
step: checkpoint.step,
|
|
611
|
+
description: checkpoint.description,
|
|
612
|
+
totalCheckpoints: checkpoints.length,
|
|
613
|
+
}, null, 2);
|
|
614
|
+
},
|
|
615
|
+
});
|
|
616
|
+
// ── checkpoint_revert ──
|
|
617
|
+
registerTool({
|
|
618
|
+
name: 'checkpoint_revert',
|
|
619
|
+
description: 'Revert to a previously saved checkpoint. Returns the checkpoint state ' +
|
|
620
|
+
'so the agent can resume from that point. All subsequent checkpoints ' +
|
|
621
|
+
'are removed. Use "list" as the id to see all available checkpoints.',
|
|
622
|
+
parameters: {
|
|
623
|
+
id: {
|
|
624
|
+
type: 'string',
|
|
625
|
+
description: 'The checkpoint ID to revert to, or "list" to see all checkpoints',
|
|
626
|
+
required: true,
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
tier: 'free',
|
|
630
|
+
execute: async (args) => {
|
|
631
|
+
const id = String(args.id || '');
|
|
632
|
+
if (id === 'list') {
|
|
633
|
+
const all = getCheckpoints();
|
|
634
|
+
if (all.length === 0) {
|
|
635
|
+
return 'No checkpoints saved in this session.';
|
|
636
|
+
}
|
|
637
|
+
return JSON.stringify(all.map(c => ({
|
|
638
|
+
id: c.id,
|
|
639
|
+
step: c.step,
|
|
640
|
+
description: c.description,
|
|
641
|
+
timestamp: c.timestamp,
|
|
642
|
+
canRevert: c.canRevert,
|
|
643
|
+
filesModified: c.state.filesModified,
|
|
644
|
+
})), null, 2);
|
|
645
|
+
}
|
|
646
|
+
const checkpoint = revertToCheckpoint(id);
|
|
647
|
+
if (!checkpoint) {
|
|
648
|
+
return `Checkpoint "${id}" not found or not revertable. Use id "list" to see available checkpoints.`;
|
|
649
|
+
}
|
|
650
|
+
return JSON.stringify({
|
|
651
|
+
status: 'reverted',
|
|
652
|
+
checkpoint: {
|
|
653
|
+
id: checkpoint.id,
|
|
654
|
+
step: checkpoint.step,
|
|
655
|
+
description: checkpoint.description,
|
|
656
|
+
state: checkpoint.state,
|
|
657
|
+
},
|
|
658
|
+
remainingCheckpoints: checkpoints.length,
|
|
659
|
+
message: `Reverted to "${checkpoint.description}". All subsequent checkpoints removed. Resume from this state.`,
|
|
660
|
+
}, null, 2);
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
// ── anticipate ──
|
|
664
|
+
registerTool({
|
|
665
|
+
name: 'anticipate',
|
|
666
|
+
description: 'Predict what the user will likely ask next based on conversation history, ' +
|
|
667
|
+
'current task context, and learned task sequences. Returns up to 3 predictions ' +
|
|
668
|
+
'with confidence scores and suggested preparations.',
|
|
669
|
+
parameters: {
|
|
670
|
+
current_task: {
|
|
671
|
+
type: 'string',
|
|
672
|
+
description: 'What the user is currently working on',
|
|
673
|
+
required: true,
|
|
674
|
+
},
|
|
675
|
+
recent_messages: {
|
|
676
|
+
type: 'string',
|
|
677
|
+
description: 'Pipe-separated (|) list of recent user messages for context',
|
|
678
|
+
required: false,
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
tier: 'free',
|
|
682
|
+
execute: async (args) => {
|
|
683
|
+
const currentTask = String(args.current_task || '');
|
|
684
|
+
const recentMessages = args.recent_messages
|
|
685
|
+
? String(args.recent_messages).split('|').map(s => s.trim()).filter(Boolean)
|
|
686
|
+
: [];
|
|
687
|
+
const predictions = anticipateNext(recentMessages, currentTask);
|
|
688
|
+
if (predictions.length === 0) {
|
|
689
|
+
return JSON.stringify({
|
|
690
|
+
predictions: [],
|
|
691
|
+
message: 'No strong predictions for next action. Waiting for more context.',
|
|
692
|
+
}, null, 2);
|
|
693
|
+
}
|
|
694
|
+
return JSON.stringify({
|
|
695
|
+
predictions: predictions.map(p => ({
|
|
696
|
+
prediction: p.prediction,
|
|
697
|
+
confidence: parseFloat(p.confidence.toFixed(2)),
|
|
698
|
+
preparation: p.preparation,
|
|
699
|
+
reasoning: p.reasoning,
|
|
700
|
+
})),
|
|
701
|
+
}, null, 2);
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
// ── identity ──
|
|
705
|
+
registerTool({
|
|
706
|
+
name: 'identity',
|
|
707
|
+
description: 'Show the agent identity, personality profile, and evolution over time. ' +
|
|
708
|
+
'The identity persists across sessions and evolves based on usage patterns. ' +
|
|
709
|
+
'Use action "show" to view, "milestone" to record an achievement, or ' +
|
|
710
|
+
'"adjust" to nudge a personality dimension.',
|
|
711
|
+
parameters: {
|
|
712
|
+
action: {
|
|
713
|
+
type: 'string',
|
|
714
|
+
description: 'Action: "show" (default), "milestone", "adjust", "summary"',
|
|
715
|
+
required: false,
|
|
716
|
+
default: 'show',
|
|
717
|
+
},
|
|
718
|
+
event: {
|
|
719
|
+
type: 'string',
|
|
720
|
+
description: 'For "milestone" action: the event to record',
|
|
721
|
+
required: false,
|
|
722
|
+
},
|
|
723
|
+
dimension: {
|
|
724
|
+
type: 'string',
|
|
725
|
+
description: 'For "adjust" action: personality dimension (verbosity, caution, creativity, autonomy)',
|
|
726
|
+
required: false,
|
|
727
|
+
},
|
|
728
|
+
direction: {
|
|
729
|
+
type: 'string',
|
|
730
|
+
description: 'For "adjust" action: "increase" or "decrease"',
|
|
731
|
+
required: false,
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
tier: 'free',
|
|
735
|
+
execute: async (args) => {
|
|
736
|
+
const action = String(args.action || 'show');
|
|
737
|
+
switch (action) {
|
|
738
|
+
case 'summary':
|
|
739
|
+
return getPersonalitySummary();
|
|
740
|
+
case 'milestone': {
|
|
741
|
+
const event = String(args.event || '');
|
|
742
|
+
if (!event)
|
|
743
|
+
return 'Error: "event" parameter required for milestone action.';
|
|
744
|
+
addMilestone(event);
|
|
745
|
+
return `Milestone recorded: "${event}"`;
|
|
746
|
+
}
|
|
747
|
+
case 'adjust': {
|
|
748
|
+
const dimension = String(args.dimension || '');
|
|
749
|
+
const direction = String(args.direction || '');
|
|
750
|
+
const validDimensions = ['verbosity', 'caution', 'creativity', 'autonomy'];
|
|
751
|
+
if (!validDimensions.includes(dimension)) {
|
|
752
|
+
return `Error: dimension must be one of: ${validDimensions.join(', ')}`;
|
|
753
|
+
}
|
|
754
|
+
if (direction !== 'increase' && direction !== 'decrease') {
|
|
755
|
+
return 'Error: direction must be "increase" or "decrease"';
|
|
756
|
+
}
|
|
757
|
+
const result = adjustPersonality(dimension, direction);
|
|
758
|
+
return `Personality adjusted: ${result.dimension} ${result.oldValue} -> ${result.newValue} (${direction}d by ${PERSONALITY_DELTA})`;
|
|
759
|
+
}
|
|
760
|
+
case 'show':
|
|
761
|
+
default: {
|
|
762
|
+
const identity = getIdentity();
|
|
763
|
+
return JSON.stringify({
|
|
764
|
+
created: identity.created,
|
|
765
|
+
totalSessions: identity.totalSessions,
|
|
766
|
+
totalMessages: identity.totalMessages,
|
|
767
|
+
totalToolCalls: identity.totalToolCalls,
|
|
768
|
+
personality: identity.personality,
|
|
769
|
+
preferences: identity.preferences,
|
|
770
|
+
recentMilestones: identity.milestones.slice(-5),
|
|
771
|
+
summary: getPersonalitySummary(),
|
|
772
|
+
}, null, 2);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
//# sourceMappingURL=temporal.js.map
|