@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.
Files changed (57) hide show
  1. package/dist/agent-protocol.d.ts +97 -0
  2. package/dist/agent-protocol.d.ts.map +1 -0
  3. package/dist/agent-protocol.js +618 -0
  4. package/dist/agent-protocol.js.map +1 -0
  5. package/dist/agent.d.ts.map +1 -1
  6. package/dist/agent.js +25 -1
  7. package/dist/agent.js.map +1 -1
  8. package/dist/architect.d.ts +44 -0
  9. package/dist/architect.d.ts.map +1 -0
  10. package/dist/architect.js +403 -0
  11. package/dist/architect.js.map +1 -0
  12. package/dist/cli.js +210 -2
  13. package/dist/cli.js.map +1 -1
  14. package/dist/confidence.d.ts +102 -0
  15. package/dist/confidence.d.ts.map +1 -0
  16. package/dist/confidence.js +693 -0
  17. package/dist/confidence.js.map +1 -0
  18. package/dist/graph-memory.d.ts +98 -0
  19. package/dist/graph-memory.d.ts.map +1 -0
  20. package/dist/graph-memory.js +926 -0
  21. package/dist/graph-memory.js.map +1 -0
  22. package/dist/ide/acp-server.js +2 -2
  23. package/dist/ide/acp-server.js.map +1 -1
  24. package/dist/intentionality.d.ts +139 -0
  25. package/dist/intentionality.d.ts.map +1 -0
  26. package/dist/intentionality.js +1092 -0
  27. package/dist/intentionality.js.map +1 -0
  28. package/dist/lsp-client.d.ts +167 -0
  29. package/dist/lsp-client.d.ts.map +1 -0
  30. package/dist/lsp-client.js +679 -0
  31. package/dist/lsp-client.js.map +1 -0
  32. package/dist/mcp-plugins.d.ts +62 -0
  33. package/dist/mcp-plugins.d.ts.map +1 -0
  34. package/dist/mcp-plugins.js +551 -0
  35. package/dist/mcp-plugins.js.map +1 -0
  36. package/dist/reasoning.d.ts +100 -0
  37. package/dist/reasoning.d.ts.map +1 -0
  38. package/dist/reasoning.js +1292 -0
  39. package/dist/reasoning.js.map +1 -0
  40. package/dist/temporal.d.ts +133 -0
  41. package/dist/temporal.d.ts.map +1 -0
  42. package/dist/temporal.js +778 -0
  43. package/dist/temporal.js.map +1 -0
  44. package/dist/tools/e2b-sandbox.d.ts +2 -0
  45. package/dist/tools/e2b-sandbox.d.ts.map +1 -0
  46. package/dist/tools/e2b-sandbox.js +460 -0
  47. package/dist/tools/e2b-sandbox.js.map +1 -0
  48. package/dist/tools/index.d.ts.map +1 -1
  49. package/dist/tools/index.js +19 -1
  50. package/dist/tools/index.js.map +1 -1
  51. package/dist/tools/lsp-tools.d.ts +2 -0
  52. package/dist/tools/lsp-tools.d.ts.map +1 -0
  53. package/dist/tools/lsp-tools.js +268 -0
  54. package/dist/tools/lsp-tools.js.map +1 -0
  55. package/dist/ui.js +1 -1
  56. package/dist/ui.js.map +1 -1
  57. package/package.json +2 -2
@@ -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