tachibot-mcp 2.23.1 → 2.23.3

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/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to TachiBot MCP will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.23.3] - 2026-06-17
9
+
10
+ ### Changed
11
+ - **GLM-5.1 → GLM-5.2** (`z-ai/glm-5.2`, released Jun 13 on OpenRouter, verified via grok_search + web): Zhipu's open-weights flagship for long-horizon tasks — usable 1M-token context (up from agentic-tier 5.1), two thinking-effort levels (High/Max). SWE-Bench Pro 62.1 (up from 58.4), Terminal-Bench 2.1 81.0 (up from 62.0); beats GPT-5.5 on several long-horizon coding benchmarks at ~1/6 the cost. List price $1.40/$4.40 per M (262K max output). Powers `glm_reason` and the `glm` juror. GLM-5.1 kept as quota fallback via `MODEL_FALLBACKS` (GLM-5.2 → GLM-5.1 → GLM-5); the `glm` substring already routes 5.2 to the 600s reasoning timeout.
12
+
13
+ ## [2.23.2] - 2026-06-15
14
+
15
+ ### Changed
16
+ - **Kimi K2.6 → K2.7-Code** (`moonshotai/kimi-k2.7-code`, released Jun 12 on OpenRouter, verified via grok_search): coding-specialized variant built on K2.6 — +21.8% on Kimi Code Bench v2 with lower token use, 262K context, native multimodal, always-thinking mode. List price $0.75/$3.50 per M. Now powers all Kimi tools — `kimi_thinking`, `kimi_code`, `kimi_decompose`, `kimi_long_context` (`openrouter-tools.ts`) and the `kimi` juror (`jury-tool.ts`). K2.6 kept as quota fallback via `MODEL_FALLBACKS` (K2.7-Code → K2.6 → K2-Thinking); the `kimi` substring already routes K2.7-Code to the 600s reasoning timeout (regression test added).
17
+
18
+ ### Notes
19
+ - No general-purpose Kimi K2.7 exists yet — K2.7-Code is the only post-K2.6 release. The coding-tuned model now also serves the reasoning/long-context tools and the kimi juror.
20
+
8
21
  ## [2.23.1] - 2026-06-11
9
22
 
10
23
  ### Changed
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ### Multi-Model AI Orchestration Platform
6
6
 
7
- [![Version](https://img.shields.io/badge/version-2.15.2-blue.svg)](https://www.npmjs.com/package/tachibot-mcp)
7
+ [![Version](https://img.shields.io/badge/version-2.23.2-blue.svg)](https://www.npmjs.com/package/tachibot-mcp)
8
8
  [![Tools](https://img.shields.io/badge/tools-57_active-brightgreen.svg)](#-tool-ecosystem-57-tools)
9
9
  [![License](https://img.shields.io/badge/license-AGPL--3.0-green.svg)](LICENSE)
10
10
  [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org)
@@ -12,7 +12,7 @@
12
12
 
13
13
  **57 AI tools. 12 providers. One protocol.**
14
14
 
15
- Orchestrate Perplexity, Grok, GPT-5.5, Gemini, Qwen, Kimi K2.6, and MiniMax M3
15
+ Orchestrate Perplexity, Grok, GPT-5.5, Gemini, Qwen, Kimi K2.7-Code, and MiniMax M3
16
16
  from Claude Code, Claude Desktop, Cursor, or any MCP client.
17
17
 
18
18
  [Get Started](#-quick-start) · [View Tools](#-tool-ecosystem-57-tools) · [Documentation](https://tachibot.com/docs)
@@ -87,12 +87,14 @@ export const GROK_MODELS = {
87
87
  _3: "grok-3", // Legacy with search: 256K→2M
88
88
  };
89
89
  // Kimi Models (Moonshot AI via OpenRouter)
90
- // K2.6 released Apr 20, 2026 - 1T MoE, leads SWE-bench Pro for long-horizon coding
91
- // K2.5 released Jan 27, 2026 - kept as fallback
90
+ // K2.7-Code released Jun 12, 2026 - coding-specialized (built on K2.6), +21.8% on Kimi Code Bench v2, lower token use, 262K ctx, multimodal, always-thinking
91
+ // K2.6 released Apr 20, 2026 - 1T MoE, leads SWE-bench Pro for long-horizon coding (fallback)
92
+ // K2.5 released Jan 27, 2026 - kept as legacy
92
93
  export const KIMI_MODELS = {
93
94
  K2_THINKING: "moonshotai/kimi-k2-thinking", // 1T MoE, 32B active - agentic reasoning (256k context)
94
- K2_6: "moonshotai/kimi-k2.6", // CURRENT: 1T MoE, SWE-Pro leader, ~$0.74/$4.65
95
- K2_5: "moonshotai/kimi-k2.5", // Previous: multimodal + agent swarm (fallback)
95
+ K2_7_CODE: "moonshotai/kimi-k2.7-code", // CURRENT (Jun 12, 2026): coding-specialized, +21.8% Code Bench v2, 262K ctx, $0.75/$3.50
96
+ K2_6: "moonshotai/kimi-k2.6", // Previous (Apr 2026): 1T MoE, SWE-Pro leader (fallback)
97
+ K2_5: "moonshotai/kimi-k2.5", // Legacy: multimodal + agent swarm
96
98
  };
97
99
  // MiniMax Models (MiniMax via OpenRouter)
98
100
  // M3 released May 31, 2026 - 1M ctx, MSA sparse attention (~1/20 compute at 1M), native multimodal
@@ -186,7 +188,7 @@ export const CURRENT_MODELS = {
186
188
  reason: PERPLEXITY_MODELS.SONAR_REASONING, // sonar-reasoning-pro $2/$8 per M
187
189
  },
188
190
  openrouter: {
189
- kimi: KIMI_MODELS.K2_6, // K2.6 (Apr 2026): 1T MoE, SWE-bench Pro leader
191
+ kimi: KIMI_MODELS.K2_7_CODE, // K2.7-Code (Jun 2026): coding-specialized, built on K2.6
190
192
  qwen: QWEN_MODELS.CODER_NEXT, // Qwen3-Coder-Next: 80B/3B MoE, 262K ctx, SWE >70% (no 3.6-coder yet)
191
193
  qwen_reason: QWEN_MODELS.MAX_THINKING, // 235B MoE thinking mode (HMMT 98%) — still best for reasoning
192
194
  minimax: MINIMAX_MODELS.M3, // M3: 1M ctx, MSA sparse attention, agentic/coding
@@ -304,7 +306,7 @@ export const TOOL_DEFAULTS = {
304
306
  temperature: 0.3, // Lower for precise reasoning
305
307
  },
306
308
  kimi_thinking: {
307
- model: KIMI_MODELS.K2_6, // K2.6 (Apr 2026): 1T MoE, SWE-bench Pro leader
309
+ model: KIMI_MODELS.K2_7_CODE, // K2.7-Code (Jun 2026): coding-specialized, built on K2.6
308
310
  maxTokens: 16000,
309
311
  temperature: 0.7,
310
312
  },
@@ -370,6 +372,7 @@ export const MODEL_DISPLAY_NAMES = {
370
372
  "sonar-reasoning-pro": "perplexity-reason",
371
373
  // Kimi (Moonshot)
372
374
  "moonshotai/kimi-k2-thinking": "kimi-k2",
375
+ "moonshotai/kimi-k2.7-code": "kimi-k2.7-code",
373
376
  "moonshotai/kimi-k2.6": "kimi-k2.6",
374
377
  "moonshotai/kimi-k2.5": "kimi-k2.5",
375
378
  "moonshotai/kimi-k2.5-thinking": "kimi-k2.5",
@@ -424,6 +427,7 @@ export const MODEL_PRICING = {
424
427
  "sonar-reasoning-pro": 0.006, // avoid - expensive reasoning tokens
425
428
  // OpenRouter models - Kimi
426
429
  "moonshotai/kimi-k2-thinking": 0.002,
430
+ "moonshotai/kimi-k2.7-code": 0.002125, // ($0.75 + $3.50) / 2 / 1000 (Jun 12, 2026)
427
431
  "moonshotai/kimi-k2.6": 0.0027, // ($0.74 + $4.65) / 2 / 1000 (Apr 2026)
428
432
  "moonshotai/kimi-k2.5": 0.003,
429
433
  "moonshotai/kimi-k2.5-thinking": 0.003,
@@ -28,7 +28,7 @@ const MODELS = {
28
28
  // OpenRouter
29
29
  QWEN: QWEN_MODELS.CODER_NEXT, // qwen/qwen3-coder-next (80B/3B MoE, 262K ctx)
30
30
  QWEN_REASON: QWEN_MODELS.MAX_THINKING, // qwen/qwen3-235b-a22b-thinking (235B MoE)
31
- KIMI: KIMI_MODELS.K2_6, // moonshotai/kimi-k2.6 (Apr 2026 - SWE-bench Pro leader)
31
+ KIMI: KIMI_MODELS.K2_7_CODE, // moonshotai/kimi-k2.7-code (Jun 2026 - coding-specialized, built on K2.6)
32
32
  MINIMAX: MINIMAX_MODELS.M3, // minimax/minimax-m3 (May 2026 - 1M ctx, MSA sparse attention)
33
33
  };
34
34
  /**
@@ -36,8 +36,8 @@ export function getTimeoutConfig() {
36
36
  * Thinking/reasoning/swarm models get extended timeout (600s default).
37
37
  * Standard models get 180s default.
38
38
  *
39
- * NOTE: All Kimi K2 variants (incl. 'moonshotai/kimi-k2.6') run the Agent Swarm
40
- * and need the extended timeout — but their model IDs don't contain 'thinking',
39
+ * NOTE: All Kimi K2 variants (incl. 'moonshotai/kimi-k2.7-code', which is always in
40
+ * thinking mode) need the extended timeout — but their model IDs don't contain 'thinking',
41
41
  * so we match 'kimi' explicitly. Same applies to DeepSeek V4 and Zhipu GLM, whose
42
42
  * reasoning passes are slow but whose IDs lack the 'thinking'/'reasoning' marker.
43
43
  * This keeps primary and fallback consistent and lets these tools inherit 600s
@@ -9,7 +9,7 @@ export * from './memory-manager.js';
9
9
  // Provider exports
10
10
  export * from './providers/local-provider.js';
11
11
  export * from './providers/mem0-provider.js';
12
- // export * from './providers/dokoro-provider.js';
12
+ export * from './providers/dokoro-provider.js';
13
13
  // export * from './providers/hybrid-provider.js';
14
14
  // Main API
15
15
  export { getMemoryManager, resetMemoryManager, HierarchicalMemoryManager } from './memory-manager.js';
@@ -109,9 +109,8 @@ export function validateMemoryConfig(config) {
109
109
  if (config.provider === 'mem0' && !config.mem0?.apiKey && !process.env.MEM0_API_KEY) {
110
110
  errors.push('Mem0 provider requires API key (MEM0_API_KEY or config.mem0.apiKey)');
111
111
  }
112
- if (config.provider === 'dokoro' && !config.dokoro?.connectionString && !process.env.DOKORO_CONNECTION) {
113
- errors.push('Dokoro provider requires connection string');
114
- }
112
+ // Dokoro is file-backed: connectionString is optional (falls back to
113
+ // DOKORO_PATH env or {cwd}/dokoro), so no validation needed here.
115
114
  if (config.provider === 'local' && !config.local?.path) {
116
115
  errors.push('Local provider requires storage path');
117
116
  }
@@ -6,7 +6,7 @@ import { mergeMemoryConfig, validateMemoryConfig } from './memory-config.js';
6
6
  import { LocalProvider } from './providers/local-provider.js';
7
7
  import { Mem0Provider } from './providers/mem0-provider.js';
8
8
  import { randomBytes } from 'crypto';
9
- // import { DokoroProvider } from './providers/dokoro-provider.js';
9
+ import { DokoroProvider } from './providers/dokoro-provider.js';
10
10
  // import { HybridProvider } from './providers/hybrid-provider.js';
11
11
  /**
12
12
  * Main memory manager that coordinates all providers
@@ -245,9 +245,15 @@ export class HierarchicalMemoryManager {
245
245
  return null;
246
246
  }
247
247
  case 'dokoro':
248
- // TODO: Implement Dokoro provider
249
- console.warn('Dokoro provider not yet implemented');
250
- return null;
248
+ try {
249
+ const provider = new DokoroProvider(this.config.dokoro || {});
250
+ await provider.initialize();
251
+ return provider;
252
+ }
253
+ catch (error) {
254
+ console.error('Failed to create Dokoro provider:', error);
255
+ return null;
256
+ }
251
257
  case 'local':
252
258
  return await this.createLocalProvider();
253
259
  case 'hybrid':
@@ -1,185 +1,169 @@
1
1
  /**
2
2
  * Dokoro Memory Provider
3
- * Integration with Dokoro for development logging and memory persistence
3
+ *
4
+ * File-backed integration with a dokoro workspace. dokoro is a local-first,
5
+ * per-project agent-memory system: there is no client library and no network
6
+ * endpoint — its workspace is a folder of markdown files with YAML frontmatter.
7
+ *
8
+ * Resolution convention (same as planner-tools.ts):
9
+ * connectionString (treated as DOKORO_PATH override)
10
+ * || process.env.DOKORO_PATH
11
+ * || {cwd}/dokoro
12
+ *
13
+ * Memories are written to <dokoroPath>/sessions/ as markdown files; reads
14
+ * cover both <dokoroPath>/sessions/ and <dokoroPath>/daily/.
15
+ *
16
+ * If the workspace folder cannot be created/written, the provider degrades
17
+ * to an in-memory store (never throws out of the provider for storage issues).
4
18
  */
5
19
  import { BaseMemoryProvider } from '../memory-interface.js';
20
+ import * as fs from 'fs/promises';
21
+ import { constants as fsConstants } from 'fs';
22
+ import * as path from 'path';
23
+ import * as yaml from 'yaml';
24
+ const MEMORY_TIERS = ['session', 'working', 'project', 'team', 'global'];
6
25
  /**
7
- * Dokoro provider implementation
26
+ * Dokoro provider implementation (file-backed)
8
27
  */
9
28
  export class DokoroProvider extends BaseMemoryProvider {
10
29
  constructor(config) {
11
30
  super();
12
31
  this.name = 'dokoro';
13
- this.client = null;
14
- this.isConnected = false;
32
+ this.fileBacked = false;
33
+ this.fallbackStore = new Map();
34
+ // Spread first, then resolve defaults — explicit-undefined fields in the
35
+ // caller's config must not clobber env/default resolution.
15
36
  this.config = {
16
- connectionString: config.connectionString || process.env.DOKORO_CONNECTION,
17
- workspace: config.workspace || process.env.DOKORO_WORKSPACE || 'default',
18
- projectId: config.projectId || process.env.DOKORO_PROJECT,
19
- enableSync: config.enableSync !== false,
20
- ...config
37
+ ...config,
38
+ connectionString: config.connectionString ?? process.env.DOKORO_CONNECTION,
39
+ workspace: config.workspace ?? process.env.DOKORO_WORKSPACE ?? 'default',
40
+ projectId: config.projectId ?? process.env.DOKORO_PROJECT,
41
+ enableSync: config.enableSync !== false
21
42
  };
43
+ // connectionString acts as a DOKORO_PATH override; same resolution
44
+ // convention as planner-tools.ts getDokoroDailyDir().
45
+ this.dokoroPath = path.resolve(this.config.connectionString ||
46
+ process.env.DOKORO_PATH ||
47
+ path.join(process.cwd(), 'dokoro'));
48
+ this.sessionsDir = path.join(this.dokoroPath, 'sessions');
49
+ this.dailyDir = path.join(this.dokoroPath, 'daily');
22
50
  }
23
51
  async doInitialize() {
24
52
  try {
25
- // Try to load Dokoro client dynamically
26
- const dokoroModule = await this.loadDokoroModule();
27
- if (!dokoroModule) {
28
- // Create a mock/local implementation if Dokoro is not available
29
- this.client = this.createLocalDokoro();
30
- }
31
- else {
32
- // Use actual Dokoro client
33
- this.client = new dokoroModule.DokoroClient({
34
- connectionString: this.config.connectionString,
35
- workspace: this.config.workspace
36
- });
37
- }
38
- // Connect to Dokoro
39
- if (this.client) {
40
- await this.client.connect();
41
- this.isConnected = true;
42
- }
43
- console.error(`Dokoro provider initialized for workspace: ${this.config.workspace}`);
53
+ await fs.mkdir(this.sessionsDir, { recursive: true });
54
+ await fs.access(this.sessionsDir, fsConstants.W_OK);
55
+ this.fileBacked = true;
56
+ console.error(`Dokoro provider: file-backed at ${this.dokoroPath}`);
44
57
  }
45
58
  catch (error) {
46
- console.error('Failed to initialize Dokoro provider:', error);
47
- // Fall back to local implementation
48
- this.client = this.createLocalDokoro();
49
- await this.client.connect();
50
- this.isConnected = true;
59
+ this.fileBacked = false;
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ console.error(`Dokoro provider: workspace unavailable at ${this.dokoroPath}, using in-memory fallback (${message})`);
51
62
  }
52
63
  }
53
64
  async store(item) {
54
- if (!this.isConnected || !this.client) {
55
- throw new Error('Dokoro provider not initialized');
65
+ if (!item.id) {
66
+ item.id = this.generateId();
56
67
  }
57
- try {
58
- // Convert MemoryItem to Dokoro entry
59
- const entry = {
60
- type: `memory_${item.tier}`,
61
- content: item.content,
62
- metadata: {
63
- ...item.metadata,
64
- memoryId: item.id,
65
- tier: item.tier,
66
- teamId: item.teamId,
67
- ttl: item.ttl,
68
- accessCount: item.accessCount
69
- },
70
- workspace: this.config.workspace,
71
- project: item.projectId || this.config.projectId,
72
- user: item.userId,
73
- tags: item.tags,
74
- timestamp: item.timestamp
75
- };
76
- // Store in Dokoro
77
- const id = await this.client.log(entry);
78
- // Update metrics
79
- this.metrics.totalItems++;
80
- this.metrics.itemsByTier[item.tier]++;
81
- return id || item.id;
82
- }
83
- catch (error) {
84
- console.error('Failed to store in Dokoro:', error);
85
- throw error;
68
+ if (!item.timestamp) {
69
+ item.timestamp = new Date();
86
70
  }
87
- }
88
- async retrieve(query) {
89
- if (!this.isConnected || !this.client) {
90
- throw new Error('Dokoro provider not initialized');
91
- }
92
- try {
93
- const startTime = Date.now();
94
- // Build Dokoro query
95
- const dokoroQuery = {
96
- workspace: this.config.workspace,
97
- project: query.projectId || this.config.projectId,
98
- search: query.text,
99
- tags: query.tags,
100
- startDate: query.startDate,
101
- endDate: query.endDate,
102
- limit: query.limit,
103
- offset: query.offset
104
- };
105
- // Add tier filtering
106
- if (query.tiers && query.tiers.length > 0) {
107
- dokoroQuery.type = query.tiers.map(tier => `memory_${tier}`).join(',');
71
+ if (this.fileBacked) {
72
+ try {
73
+ const filePath = await this.writeMemoryFile(item);
74
+ this.metrics.totalItems++;
75
+ this.metrics.itemsByTier[item.tier]++;
76
+ // The file basename (without .md) is the durable id for file-backed
77
+ // items. Sync it back onto the caller's item so code holding the
78
+ // pre-store id via item.id can still update()/delete() later.
79
+ item.id = path.basename(filePath, '.md');
80
+ return item.id;
108
81
  }
109
- // Query Dokoro
110
- const entries = await this.client.query(dokoroQuery);
111
- // Convert to MemoryItems
112
- const items = this.convertDokoroEntries(entries);
113
- // Filter by userId if specified
114
- let filteredItems = items;
115
- if (query.userId) {
116
- filteredItems = items.filter(item => item.userId === query.userId);
82
+ catch (error) {
83
+ // Flip to in-memory mode so store/retrieve/update/delete switch
84
+ // together leaving fileBacked true would strand this item in the
85
+ // fallback store where retrieve() (file scan) never sees it.
86
+ this.fileBacked = false;
87
+ console.error('Dokoro provider: failed to write memory file, switching to in-memory fallback for this session:', error);
117
88
  }
118
- // Update metrics
119
- const retrievalTime = Date.now() - startTime;
120
- this.metrics.avgRetrievalTime =
121
- (this.metrics.avgRetrievalTime + retrievalTime) / 2;
122
- // Update access counts
123
- filteredItems.forEach(item => {
124
- item.accessCount = (item.accessCount || 0) + 1;
125
- item.lastAccessed = new Date();
126
- });
127
- return filteredItems;
128
- }
129
- catch (error) {
130
- console.error('Failed to retrieve from Dokoro:', error);
131
- throw error;
132
89
  }
90
+ this.fallbackStore.set(item.id, item);
91
+ this.metrics.totalItems++;
92
+ this.metrics.itemsByTier[item.tier]++;
93
+ return item.id;
94
+ }
95
+ async retrieve(query) {
96
+ const startTime = Date.now();
97
+ const items = this.fileBacked
98
+ ? await this.readAllMemoryFiles()
99
+ : Array.from(this.fallbackStore.values());
100
+ const results = this.applyQuery(items, query);
101
+ // Update metrics
102
+ const retrievalTime = Date.now() - startTime;
103
+ this.metrics.avgRetrievalTime =
104
+ (this.metrics.avgRetrievalTime + retrievalTime) / 2;
105
+ // Update access counts (in-memory only; not persisted back to files)
106
+ results.forEach(item => {
107
+ item.accessCount = (item.accessCount || 0) + 1;
108
+ item.lastAccessed = new Date();
109
+ });
110
+ return results;
133
111
  }
134
112
  async update(id, updates) {
135
- if (!this.isConnected || !this.client) {
136
- throw new Error('Dokoro provider not initialized');
113
+ if (!this.fileBacked) {
114
+ const item = this.fallbackStore.get(id);
115
+ if (!item)
116
+ return false;
117
+ Object.assign(item, updates, { id: item.id });
118
+ return true;
137
119
  }
138
120
  try {
139
- // Convert updates to Dokoro format
140
- const dokoroUpdates = {
141
- content: updates.content,
142
- metadata: updates.metadata,
143
- tags: updates.tags
144
- };
145
- // Update tier in metadata if changed
146
- if (updates.tier) {
147
- dokoroUpdates.type = `memory_${updates.tier}`;
148
- dokoroUpdates.metadata = {
149
- ...dokoroUpdates.metadata,
150
- tier: updates.tier
151
- };
152
- }
153
- return await this.client.update(id, dokoroUpdates);
121
+ const filePath = await this.findFileById(id);
122
+ if (!filePath)
123
+ return false;
124
+ const item = await this.parseMemoryFile(filePath);
125
+ if (!item)
126
+ return false;
127
+ Object.assign(item, updates, { id: item.id });
128
+ await this.writeMemoryFile(item, filePath);
129
+ return true;
154
130
  }
155
131
  catch (error) {
156
- console.error('Failed to update in Dokoro:', error);
132
+ console.error('Dokoro provider: failed to update memory file:', error);
157
133
  return false;
158
134
  }
159
135
  }
160
136
  async delete(id) {
161
- if (!this.isConnected || !this.client) {
162
- throw new Error('Dokoro provider not initialized');
137
+ if (!this.fileBacked) {
138
+ const item = this.fallbackStore.get(id);
139
+ if (!item)
140
+ return false;
141
+ this.fallbackStore.delete(id);
142
+ this.metrics.totalItems--;
143
+ this.metrics.itemsByTier[item.tier]--;
144
+ return true;
163
145
  }
164
146
  try {
165
- const result = await this.client.delete(id);
166
- if (result) {
167
- this.metrics.totalItems--;
147
+ const filePath = await this.findFileById(id);
148
+ if (!filePath)
149
+ return false;
150
+ const item = await this.parseMemoryFile(filePath);
151
+ await fs.unlink(filePath);
152
+ this.metrics.totalItems--;
153
+ if (item) {
154
+ this.metrics.itemsByTier[item.tier]--;
168
155
  }
169
- return result;
156
+ return true;
170
157
  }
171
158
  catch (error) {
172
- console.error('Failed to delete from Dokoro:', error);
159
+ console.error('Dokoro provider: failed to delete memory file:', error);
173
160
  return false;
174
161
  }
175
162
  }
176
163
  async cleanup() {
177
164
  const now = Date.now();
178
165
  let cleaned = 0;
179
- // Query for expired items
180
- const items = await this.retrieve({
181
- endDate: new Date(now - 24 * 60 * 60 * 1000) // Items older than 24 hours
182
- });
166
+ const items = await this.export();
183
167
  for (const item of items) {
184
168
  if (item.ttl && item.ttl > 0) {
185
169
  const expiryTime = item.timestamp.getTime() + (item.ttl * 60 * 1000);
@@ -193,22 +177,10 @@ export class DokoroProvider extends BaseMemoryProvider {
193
177
  return cleaned;
194
178
  }
195
179
  async export() {
196
- if (!this.isConnected || !this.client) {
197
- throw new Error('Dokoro provider not initialized');
198
- }
199
- try {
200
- const entries = await this.client.query({
201
- workspace: this.config.workspace,
202
- project: this.config.projectId,
203
- type: 'memory_', // Prefix match for all memory types
204
- limit: 100000 // Large limit to get all
205
- });
206
- return this.convertDokoroEntries(entries);
207
- }
208
- catch (error) {
209
- console.error('Failed to export from Dokoro:', error);
210
- throw error;
180
+ if (this.fileBacked) {
181
+ return await this.readAllMemoryFiles();
211
182
  }
183
+ return Array.from(this.fallbackStore.values());
212
184
  }
213
185
  async import(items) {
214
186
  let imported = 0;
@@ -224,149 +196,281 @@ export class DokoroProvider extends BaseMemoryProvider {
224
196
  return imported;
225
197
  }
226
198
  async isAvailable() {
227
- if (!this.isConnected || !this.client)
228
- return false;
199
+ return this.initialized;
200
+ }
201
+ async close() {
202
+ this.fallbackStore.clear();
203
+ await super.close();
204
+ }
205
+ /**
206
+ * File helpers
207
+ */
208
+ /**
209
+ * Write a MemoryItem as a dokoro markdown file (YAML frontmatter + body).
210
+ * If targetPath is given, overwrites that file (update); otherwise a new
211
+ * file is created in sessions/ with a collision-safe name.
212
+ */
213
+ async writeMemoryFile(item, targetPath) {
214
+ const filePath = targetPath || await this.nextAvailablePath(item);
215
+ const tags = Array.from(new Set(['tachibot', ...(item.tags || [])]));
216
+ const frontmatter = {
217
+ title: this.titleFor(item),
218
+ date: item.timestamp.toISOString(),
219
+ type: item.metadata?.kind || 'memory',
220
+ status: 'active',
221
+ provider: 'tachibot',
222
+ tier: item.tier,
223
+ memoryId: item.id,
224
+ tags
225
+ };
226
+ if (item.userId)
227
+ frontmatter.userId = item.userId;
228
+ if (item.projectId || this.config.projectId) {
229
+ frontmatter.projectId = item.projectId || this.config.projectId;
230
+ }
231
+ if (item.teamId)
232
+ frontmatter.teamId = item.teamId;
233
+ if (item.ttl !== undefined)
234
+ frontmatter.ttl = item.ttl;
235
+ if (item.accessCount !== undefined)
236
+ frontmatter.accessCount = item.accessCount;
237
+ if (item.metadata && Object.keys(item.metadata).length > 0) {
238
+ frontmatter.metadata = item.metadata;
239
+ }
240
+ const content = `---\n${yaml.stringify(frontmatter)}---\n\n${item.content}\n`;
241
+ // Write to temp file then rename (atomic, mirrors local-provider.ts)
242
+ const tempPath = `${filePath}.tmp`;
243
+ await fs.writeFile(tempPath, content, 'utf-8');
244
+ await fs.rename(tempPath, filePath);
245
+ return filePath;
246
+ }
247
+ /**
248
+ * Build a collision-safe sessions/ path:
249
+ * YYYY-MM-DD-HHhMMm-dayname-memory-<slug>.md (dokoro naming convention,
250
+ * same pattern as planner-tools.ts generatePlanFilename).
251
+ */
252
+ async nextAvailablePath(item) {
253
+ const ts = item.timestamp;
254
+ const year = ts.getFullYear();
255
+ const month = String(ts.getMonth() + 1).padStart(2, '0');
256
+ const day = String(ts.getDate()).padStart(2, '0');
257
+ const hours = String(ts.getHours()).padStart(2, '0');
258
+ const minutes = String(ts.getMinutes()).padStart(2, '0');
259
+ const dayName = ts.toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase();
260
+ const slug = this.slugify(item.metadata?.title ||
261
+ item.metadata?.kind ||
262
+ item.content ||
263
+ item.tier);
264
+ const base = `${year}-${month}-${day}-${hours}h${minutes}m-${dayName}-memory-${slug}`;
265
+ let candidate = path.join(this.sessionsDir, `${base}.md`);
266
+ let counter = 2;
267
+ while (await this.fileExists(candidate)) {
268
+ candidate = path.join(this.sessionsDir, `${base}-${counter}.md`);
269
+ counter++;
270
+ }
271
+ return candidate;
272
+ }
273
+ slugify(text) {
274
+ return (text
275
+ .toLowerCase()
276
+ .replace(/[^a-z0-9]+/g, '-')
277
+ .replace(/^-|-$/g, '')
278
+ .substring(0, 40) || 'item');
279
+ }
280
+ titleFor(item) {
281
+ if (item.metadata?.title)
282
+ return String(item.metadata.title);
283
+ const firstLine = item.content.split('\n')[0].trim();
284
+ if (firstLine) {
285
+ return firstLine.length > 80 ? `${firstLine.substring(0, 77)}...` : firstLine;
286
+ }
287
+ return `Memory (${item.tier})`;
288
+ }
289
+ async fileExists(filePath) {
229
290
  try {
230
- // Check connection by getting stats
231
- await this.client.getStats();
291
+ await fs.access(filePath);
232
292
  return true;
233
293
  }
234
- catch (error) {
294
+ catch {
235
295
  return false;
236
296
  }
237
297
  }
238
- async close() {
239
- if (this.client) {
240
- await this.client.disconnect();
241
- this.client = null;
298
+ /**
299
+ * List all markdown files in sessions/ and daily/.
300
+ */
301
+ async listMemoryFiles() {
302
+ const files = [];
303
+ for (const dir of [this.sessionsDir, this.dailyDir]) {
304
+ try {
305
+ const entries = await fs.readdir(dir);
306
+ for (const entry of entries) {
307
+ if (entry.endsWith('.md')) {
308
+ files.push(path.join(dir, entry));
309
+ }
310
+ }
311
+ }
312
+ catch {
313
+ // Directory missing (e.g. no daily/ yet) — skip silently
314
+ }
242
315
  }
243
- this.isConnected = false;
244
- await super.close();
316
+ return files;
317
+ }
318
+ async readAllMemoryFiles() {
319
+ const files = await this.listMemoryFiles();
320
+ const items = [];
321
+ for (const filePath of files) {
322
+ const item = await this.parseMemoryFile(filePath);
323
+ if (item) {
324
+ items.push(item);
325
+ }
326
+ }
327
+ return items;
245
328
  }
246
329
  /**
247
- * Helper methods
330
+ * Resolve a stored id (file basename without .md) back to its path.
248
331
  */
249
- async loadDokoroModule() {
250
- try {
251
- // Try to dynamically import dokoro package
252
- // @ts-ignore - Optional dependency
253
- const dokoro = await import('dokoro');
254
- return dokoro;
255
- }
256
- catch (error) {
257
- // Dokoro package not installed
258
- console.warn('Dokoro package not installed. Using local implementation.');
259
- return null;
332
+ async findFileById(id) {
333
+ const fileName = `${id}.md`;
334
+ for (const dir of [this.sessionsDir, this.dailyDir]) {
335
+ const candidate = path.join(dir, fileName);
336
+ if (await this.fileExists(candidate)) {
337
+ return candidate;
338
+ }
260
339
  }
340
+ return null;
261
341
  }
262
- createLocalDokoro() {
263
- // Local implementation that mimics Dokoro API
264
- const localStore = new Map();
265
- let nextId = 1;
266
- return {
267
- async connect() {
268
- console.error('Using local Dokoro implementation');
269
- },
270
- async disconnect() {
271
- localStore.clear();
272
- },
273
- async log(entry) {
274
- const id = entry.id || `dokoro_${nextId++}`;
275
- entry.id = id;
276
- entry.timestamp = entry.timestamp || new Date();
277
- localStore.set(id, entry);
278
- return id;
279
- },
280
- async query(params) {
281
- let results = Array.from(localStore.values());
282
- // Filter by type
283
- if (params.type) {
284
- const types = params.type.split(',');
285
- results = results.filter(e => types.some(t => e.type.startsWith(t)));
286
- }
287
- // Filter by workspace
288
- if (params.workspace) {
289
- results = results.filter(e => e.workspace === params.workspace);
290
- }
291
- // Filter by project
292
- if (params.project) {
293
- results = results.filter(e => e.project === params.project);
294
- }
295
- // Text search
296
- if (params.search) {
297
- const searchLower = params.search.toLowerCase();
298
- results = results.filter(e => e.content.toLowerCase().includes(searchLower));
299
- }
300
- // Filter by tags
301
- if (params.tags && params.tags.length > 0) {
302
- results = results.filter(e => e.tags && params.tags.some(tag => e.tags.includes(tag)));
303
- }
304
- // Date filtering
305
- if (params.startDate) {
306
- results = results.filter(e => e.timestamp && e.timestamp >= params.startDate);
307
- }
308
- if (params.endDate) {
309
- results = results.filter(e => e.timestamp && e.timestamp <= params.endDate);
310
- }
311
- // Sort by timestamp (newest first)
312
- results.sort((a, b) => {
313
- const timeA = a.timestamp?.getTime() || 0;
314
- const timeB = b.timestamp?.getTime() || 0;
315
- return timeB - timeA;
316
- });
317
- // Apply offset and limit
318
- if (params.offset) {
319
- results = results.slice(params.offset);
342
+ /**
343
+ * Parse a dokoro markdown file (YAML frontmatter + body) into a MemoryItem.
344
+ * Files without frontmatter are treated as plain content.
345
+ */
346
+ async parseMemoryFile(filePath) {
347
+ try {
348
+ const raw = await fs.readFile(filePath, 'utf-8');
349
+ let frontmatter = {};
350
+ let body = raw;
351
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
352
+ if (match) {
353
+ try {
354
+ frontmatter = yaml.parse(match[1]) || {};
320
355
  }
321
- if (params.limit) {
322
- results = results.slice(0, params.limit);
356
+ catch {
357
+ frontmatter = {};
323
358
  }
324
- return results;
325
- },
326
- async update(id, updates) {
327
- const entry = localStore.get(id);
328
- if (!entry)
329
- return false;
330
- Object.assign(entry, updates);
331
- localStore.set(id, entry);
332
- return true;
333
- },
334
- async delete(id) {
335
- return localStore.delete(id);
336
- },
337
- async getStats() {
338
- const entriesByType = {};
339
- localStore.forEach(entry => {
340
- entriesByType[entry.type] = (entriesByType[entry.type] || 0) + 1;
341
- });
342
- return {
343
- totalEntries: localStore.size,
344
- entriesByType,
345
- storageUsedMB: localStore.size * 0.001 // Rough estimate
346
- };
359
+ body = raw.slice(match[0].length);
347
360
  }
348
- };
349
- }
350
- convertDokoroEntries(entries) {
351
- return entries.map(entry => {
352
- // Extract tier from type (e.g., "memory_session" -> "session")
353
- const tier = (entry.type.replace('memory_', '') || 'session');
361
+ const tier = MEMORY_TIERS.includes(frontmatter.tier)
362
+ ? frontmatter.tier
363
+ : 'session';
364
+ let timestamp;
365
+ if (frontmatter.date && !isNaN(Date.parse(frontmatter.date))) {
366
+ timestamp = new Date(frontmatter.date);
367
+ }
368
+ else {
369
+ const stats = await fs.stat(filePath);
370
+ timestamp = stats.mtime;
371
+ }
372
+ const metadata = {
373
+ ...(typeof frontmatter.metadata === 'object' && frontmatter.metadata !== null
374
+ ? frontmatter.metadata
375
+ : {}),
376
+ sourceFile: filePath
377
+ };
378
+ if (frontmatter.title && metadata.title === undefined) {
379
+ metadata.title = frontmatter.title;
380
+ }
381
+ if (frontmatter.type)
382
+ metadata.kind = frontmatter.type;
354
383
  return {
355
- id: entry.id || this.generateId(),
356
- content: entry.content,
384
+ id: path.basename(filePath, '.md'),
385
+ content: body.trim(),
357
386
  tier,
358
- userId: entry.user,
359
- projectId: entry.project || this.config.projectId,
360
- teamId: entry.metadata?.teamId,
361
- timestamp: entry.timestamp || new Date(),
362
- metadata: entry.metadata || {},
363
- tags: entry.tags,
364
- ttl: entry.metadata?.ttl,
365
- accessCount: entry.metadata?.accessCount || 0,
366
- lastAccessed: entry.metadata?.lastAccessed ?
367
- new Date(entry.metadata.lastAccessed) : new Date()
387
+ userId: frontmatter.userId,
388
+ projectId: frontmatter.projectId || this.config.projectId,
389
+ teamId: frontmatter.teamId,
390
+ timestamp,
391
+ metadata,
392
+ tags: this.normalizeTags(frontmatter.tags),
393
+ ttl: typeof frontmatter.ttl === 'number' ? frontmatter.ttl : undefined,
394
+ accessCount: typeof frontmatter.accessCount === 'number' ? frontmatter.accessCount : 0,
395
+ lastAccessed: undefined
368
396
  };
369
- });
397
+ }
398
+ catch (error) {
399
+ console.error(`Dokoro provider: failed to parse ${filePath}:`, error);
400
+ return null;
401
+ }
402
+ }
403
+ /**
404
+ * Tags in dokoro files may be a YAML list or a map (e.g. plans use
405
+ * `tags:\n type: plan`). Normalize both to string arrays.
406
+ */
407
+ normalizeTags(tags) {
408
+ if (Array.isArray(tags)) {
409
+ return tags.map(t => String(t));
410
+ }
411
+ if (tags && typeof tags === 'object') {
412
+ return Object.entries(tags).map(([key, value]) => `${key}:${value}`);
413
+ }
414
+ return undefined;
415
+ }
416
+ /**
417
+ * Query filtering shared by file-backed and fallback modes.
418
+ * Field filters (project/user/team) are lenient: items without the field
419
+ * (e.g. plans in daily/ not written by tachibot) are not excluded.
420
+ */
421
+ applyQuery(items, query) {
422
+ let results = items;
423
+ if (query.tiers && query.tiers.length > 0) {
424
+ results = results.filter(item => query.tiers.includes(item.tier));
425
+ }
426
+ if (query.projectId) {
427
+ if (this.config.strictProjectFilter) {
428
+ results = results.filter(item => item.projectId === query.projectId);
429
+ }
430
+ else {
431
+ // Deliberately lenient default: dokoro daily/ files (plans, work
432
+ // records) carry no projectId in their frontmatter, so exact-match
433
+ // filtering would hide the entire dokoro workspace whenever the
434
+ // memory manager injects its default projectId into queries. The
435
+ // tradeoff is that untagged files from other projects sharing the
436
+ // same workspace leak into results. Set strictProjectFilter: true
437
+ // in DokoroConfig for exact-match project isolation.
438
+ results = results.filter(item => !item.projectId || item.projectId === query.projectId);
439
+ }
440
+ }
441
+ if (query.userId) {
442
+ results = results.filter(item => !item.userId || item.userId === query.userId);
443
+ }
444
+ if (query.teamId) {
445
+ results = results.filter(item => !item.teamId || item.teamId === query.teamId);
446
+ }
447
+ if (query.tags && query.tags.length > 0) {
448
+ results = results.filter(item => item.tags && query.tags.some(tag => item.tags.includes(tag)));
449
+ }
450
+ if (query.startDate) {
451
+ results = results.filter(item => item.timestamp >= query.startDate);
452
+ }
453
+ if (query.endDate) {
454
+ results = results.filter(item => item.timestamp <= query.endDate);
455
+ }
456
+ // Case-insensitive substring match over title + body
457
+ if (query.text) {
458
+ const searchText = query.text.toLowerCase();
459
+ results = results.filter(item => {
460
+ const title = String(item.metadata?.title || '').toLowerCase();
461
+ return (item.content.toLowerCase().includes(searchText) ||
462
+ title.includes(searchText));
463
+ });
464
+ }
465
+ // Sort by timestamp (newest first)
466
+ results.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
467
+ if (query.offset) {
468
+ results = results.slice(query.offset);
469
+ }
470
+ if (query.limit) {
471
+ results = results.slice(0, query.limit);
472
+ }
473
+ return results;
370
474
  }
371
475
  }
372
476
  /**
@@ -54,9 +54,9 @@ export const JUROR_REGISTRY = {
54
54
  label: "Kimi (Step-by-Step)",
55
55
  role: "Think step-by-step. Decompose the problem. Find edge cases others miss.",
56
56
  call: async (q) => callOpenRouter([
57
- { role: "system", content: `You are Kimi K2.6. Think step-by-step. Decompose problems. Find edge cases. ${FORMAT_INSTRUCTION}` },
57
+ { role: "system", content: `You are Kimi K2.7-Code. Think step-by-step. Decompose problems. Find edge cases. ${FORMAT_INSTRUCTION}` },
58
58
  { role: "user", content: q }
59
- ], OpenRouterModel.KIMI_K2_6, 0.4, 3000, { top_p: 0.9 }, 240000),
59
+ ], OpenRouterModel.KIMI_K2_7_CODE, 0.4, 3000, { top_p: 0.9 }, 240000),
60
60
  },
61
61
  perplexity: {
62
62
  label: "Perplexity (Research)",
@@ -86,12 +86,12 @@ export const JUROR_REGISTRY = {
86
86
  ], OpenRouterModel.DEEPSEEK_V4_PRO, 0.3, 4000),
87
87
  },
88
88
  glm: {
89
- label: "GLM-5.1 (Agentic)",
89
+ label: "GLM-5.2 (Agentic)",
90
90
  role: "Reason as an agent: plan, anticipate failure modes, decide. Strong on tool-use and SWE.",
91
91
  call: async (q) => callOpenRouter([
92
- { role: "system", content: `You are Zhipu GLM-5.1, a SWE-Bench Pro leader. Plan, reason through tool-use/steps, then give a decisive verdict. ${FORMAT_INSTRUCTION}` },
92
+ { role: "system", content: `You are Zhipu GLM-5.2, a frontier open-weights agentic model (1M ctx, top long-horizon coding). Plan, reason through tool-use/steps, then give a decisive verdict. ${FORMAT_INSTRUCTION}` },
93
93
  { role: "user", content: q }
94
- ], OpenRouterModel.GLM_5_1, 0.3, 4000),
94
+ ], OpenRouterModel.GLM_5_2, 0.3, 4000),
95
95
  },
96
96
  stepfun: {
97
97
  label: "StepFun 3.7 (Efficient Reasoning)",
@@ -29,6 +29,7 @@ export var OpenRouterModel;
29
29
  OpenRouterModel["QWEN3_MAX_THINKING"] = "qwen/qwen3-235b-a22b-thinking-2507";
30
30
  // Moonshot AI models (Kimi)
31
31
  OpenRouterModel["KIMI_K2_THINKING"] = "moonshotai/kimi-k2-thinking";
32
+ OpenRouterModel["KIMI_K2_7_CODE"] = "moonshotai/kimi-k2.7-code";
32
33
  OpenRouterModel["KIMI_K2_6"] = "moonshotai/kimi-k2.6";
33
34
  OpenRouterModel["KIMI_K2_5"] = "moonshotai/kimi-k2.5";
34
35
  // MiniMax models - VERY CHEAP, #1 AI Intelligence Index
@@ -40,7 +41,8 @@ export var OpenRouterModel;
40
41
  OpenRouterModel["DEEPSEEK_V4_PRO"] = "deepseek/deepseek-v4-pro";
41
42
  OpenRouterModel["DEEPSEEK_V4_FLASH"] = "deepseek/deepseek-v4-flash";
42
43
  OpenRouterModel["DEEPSEEK_R1_0528"] = "deepseek/deepseek-r1-0528";
43
- // Zhipu GLM models - agentic / SWE-Bench Pro leader
44
+ // Zhipu GLM models - agentic / long-horizon coding (open-weights)
45
+ OpenRouterModel["GLM_5_2"] = "z-ai/glm-5.2";
44
46
  OpenRouterModel["GLM_5_1"] = "z-ai/glm-5.1";
45
47
  OpenRouterModel["GLM_5"] = "z-ai/glm-5";
46
48
  // StepFun models - efficient reasoning (196B, high AIME/SWE-Verified)
@@ -53,8 +55,10 @@ export var OpenRouterModel;
53
55
  const MODEL_FALLBACKS = {
54
56
  [OpenRouterModel.QWEN3_CODER_NEXT]: OpenRouterModel.QWEN3_CODER, // Fall back to 480B if Coder-Next fails
55
57
  [OpenRouterModel.QWEN3_CODER]: OpenRouterModel.QWEN3_CODER,
58
+ [OpenRouterModel.KIMI_K2_7_CODE]: OpenRouterModel.KIMI_K2_6, // Fall back to K2.6 if K2.7-Code fails
56
59
  [OpenRouterModel.KIMI_K2_6]: OpenRouterModel.KIMI_K2_THINKING, // Fall back to k2-thinking if K2.6 fails (k2.5 retired from OpenRouter)
57
60
  [OpenRouterModel.DEEPSEEK_V4_PRO]: OpenRouterModel.DEEPSEEK_V4_FLASH, // Fall back to V4 Flash if Pro is rate-limited
61
+ [OpenRouterModel.GLM_5_2]: OpenRouterModel.GLM_5_1, // Fall back to GLM-5.1 if 5.2 fails
58
62
  [OpenRouterModel.GLM_5_1]: OpenRouterModel.GLM_5, // Fall back to GLM-5 base if 5.1 fails
59
63
  [OpenRouterModel.STEPFUN_3_7]: OpenRouterModel.STEPFUN_3_5, // Fall back to 3.5 flash if 3.7 fails
60
64
  [OpenRouterModel.MINIMAX_M3]: OpenRouterModel.MINIMAX_M2_7, // Fall back to M2.7 if M3 fails
@@ -538,13 +542,13 @@ ${FORMAT_INSTRUCTION}`
538
542
  }
539
543
  });
540
544
  /**
541
- * Kimi K2.6 Thinking Tool
542
- * Multimodal model with Agent Swarm (100 sub-agents) and built-in reasoning
543
- * $0.60/$3.00 per M tokens, 262K context
545
+ * Kimi K2.7-Code Thinking Tool
546
+ * Coding-specialized multimodal model (built on K2.6), always-thinking mode
547
+ * $0.75/$3.50 per M tokens, 262K context
544
548
  */
545
549
  export const kimiThinkingTool = defineModelTool({
546
550
  name: "kimi_thinking",
547
- description: "Kimi K2.6 multimodal reasoning with Agent Swarm. Put your PROBLEM in the 'problem' parameter.",
551
+ description: "Kimi K2.7-Code multimodal reasoning (always-thinking). Put your PROBLEM in the 'problem' parameter.",
548
552
  parameters: z.object({
549
553
  problem: z.string().describe("The problem to reason about (REQUIRED - put your question here)"),
550
554
  ...reasoningContextField,
@@ -568,7 +572,7 @@ export const kimiThinkingTool = defineModelTool({
568
572
  const messages = [
569
573
  {
570
574
  role: "system",
571
- content: `You are Kimi K2, an expert reasoning model. Be concise and direct.
575
+ content: `You are Kimi K2.7-Code, an expert reasoning model. Be concise and direct.
572
576
  ${approachPrompts[args.approach || 'step-by-step']}.
573
577
  Use ${args.maxSteps} reasoning steps max. ${args.context ? `Context: ${args.context}` : ''}
574
578
  ${FORMAT_INSTRUCTION}`
@@ -582,7 +586,7 @@ ${FORMAT_INSTRUCTION}`
582
586
  // Timeout inherited from getOpenRouterModelTimeout() — Kimi → 600s (Agent Swarm
583
587
  // needs the headroom; 180s/240s previously caused timeouts).
584
588
  const reportFn = reportProgress ?? (async () => { });
585
- return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_6, 0.4, 3000, {
589
+ return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_7_CODE, 0.4, 3000, {
586
590
  top_p: 0.9,
587
591
  presence_penalty: 0.1,
588
592
  frequency_penalty: 0.2
@@ -591,12 +595,12 @@ ${FORMAT_INSTRUCTION}`
591
595
  });
592
596
  /**
593
597
  * Kimi Code Tool
594
- * SWE-focused code generation/fixing with Kimi K2.6 (SWE-Bench 76.8%)
598
+ * SWE-focused code generation/fixing with Kimi K2.7-Code (+21.8% on Kimi Code Bench v2 over K2.6)
595
599
  * Best for: code generation, bug fixing, refactoring, repo-level understanding
596
600
  */
597
601
  export const kimiCodeTool = defineModelTool({
598
602
  name: "kimi_code",
599
- description: "SWE-focused code generation/fixing with Kimi K2.6 (SWE-Bench 76.8%). Put your REQUEST in the 'query' parameter.",
603
+ description: "SWE-focused code generation/fixing with Kimi K2.7-Code (coding-specialized). Put your REQUEST in the 'query' parameter.",
600
604
  parameters: z.object({
601
605
  query: z.string().describe("Your request or question (REQUIRED - put your main request here)"),
602
606
  task: z.enum(["generate", "fix", "review", "optimize", "debug", "refactor"])
@@ -616,7 +620,7 @@ export const kimiCodeTool = defineModelTool({
616
620
  debug: "Debug and identify root causes",
617
621
  refactor: "Refactor for better structure and maintainability"
618
622
  };
619
- const systemPrompt = `You are Kimi K2.6, an expert SWE model (SWE-Bench 76.8%). You excel at repo-level code understanding and changes.
623
+ const systemPrompt = `You are Kimi K2.7-Code, a coding-specialized SWE model. You excel at repo-level code understanding and changes.
620
624
  Task: ${taskPrompts[args.task || "review"]}
621
625
  ${args.language ? `Language: ${args.language}` : ''}
622
626
  Focus: Clean code, correct solutions, minimal changes for fixes. Understand the full repo context when reviewing.
@@ -632,7 +636,7 @@ ${FORMAT_INSTRUCTION}`;
632
636
  { role: "user", content: userPrompt + fileContext }
633
637
  ];
634
638
  const reportFn = reportProgress ?? (async () => { });
635
- return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_6, 0.3, 4000), reportFn, 240000);
639
+ return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_7_CODE, 0.3, 4000), reportFn, 240000);
636
640
  }
637
641
  });
638
642
  // kimi_decompose configuration
@@ -641,12 +645,12 @@ const DECOMPOSE_MAX_TOKENS = 4500;
641
645
  // Timeout inherited from getOpenRouterModelTimeout() — Kimi → 600s (Agent Swarm).
642
646
  /**
643
647
  * Kimi Decompose Tool
644
- * Structured task decomposition using Kimi K2.6's Agent Swarm reasoning
648
+ * Structured task decomposition using Kimi K2.7-Code's extended reasoning
645
649
  * Best for: breaking complex tasks into subtasks with dependencies and acceptance criteria
646
650
  */
647
651
  export const kimiDecomposeTool = defineModelTool({
648
652
  name: "kimi_decompose",
649
- description: "Structured task decomposition with Kimi K2.6 Agent Swarm reasoning. Breaks tasks into subtasks with IDs, dependencies, and acceptance criteria.",
653
+ description: "Structured task decomposition with Kimi K2.7-Code extended reasoning. Breaks tasks into subtasks with IDs, dependencies, and acceptance criteria.",
650
654
  parameters: z.object({
651
655
  task: z.string().describe("The task to decompose (REQUIRED - describe the complex task)"),
652
656
  context: z.string().optional().describe("Additional context about the project, codebase, or constraints"),
@@ -755,7 +759,7 @@ TASK REQUIREMENTS (one block per task)
755
759
  RISKS
756
760
  - [risk] > Mitigation: [approach]`
757
761
  };
758
- const systemPrompt = `You are Kimi K2.6, expert at structured task decomposition using Agent Swarm reasoning.
762
+ const systemPrompt = `You are Kimi K2.7-Code, expert at structured task decomposition using extended reasoning.
759
763
 
760
764
  Your job is to produce SMART decompositions — not mechanical ID/deps lists, but context-aware plans that a developer can act on immediately.
761
765
 
@@ -793,7 +797,7 @@ Wrap your final output in <output> tags. Everything outside <output> is discarde
793
797
  { role: "user", content: userPrompt }
794
798
  ];
795
799
  const reportFn = reportProgress ?? (async () => { });
796
- const raw = await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_6, DECOMPOSE_TEMPERATURE, DECOMPOSE_MAX_TOKENS, {}), reportFn);
800
+ const raw = await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_7_CODE, DECOMPOSE_TEMPERATURE, DECOMPOSE_MAX_TOKENS, {}), reportFn);
797
801
  // Strip reasoning leak from Kimi K2.6 (dumps CoT into message.content)
798
802
  // Strategy: try <output> tags first, then find first section header
799
803
  const outputMatch = raw.match(/<output>([\s\S]*?)<\/output>/);
@@ -808,12 +812,12 @@ Wrap your final output in <output> tags. Everything outside <output> is discarde
808
812
  });
809
813
  /**
810
814
  * Kimi Long Context Tool
811
- * Long-context analysis leveraging Kimi K2.6's 256K context window
815
+ * Long-context analysis leveraging Kimi K2.7-Code's 262K context window
812
816
  * Best for: analyzing large documents, codebases, or text bodies
813
817
  */
814
818
  export const kimiLongContextTool = defineModelTool({
815
819
  name: "kimi_long_context",
816
- description: "Long-context analysis with Kimi K2.6 (256K context window). Put CONTENT in the 'content' parameter.",
820
+ description: "Long-context analysis with Kimi K2.7-Code (262K context window). Put CONTENT in the 'content' parameter.",
817
821
  parameters: z.object({
818
822
  content: z.string().describe("The long text/document to analyze (REQUIRED - put your content here)"),
819
823
  task: z.enum(["summarize", "extract", "analyze", "compare", "find"])
@@ -838,7 +842,7 @@ export const kimiLongContextTool = defineModelTool({
838
842
  detailed: "Provide thorough analysis with supporting evidence and examples",
839
843
  structured: "Use clear sections with headers, bullet points, and structured formatting"
840
844
  };
841
- const systemPrompt = `You are Kimi K2.6, expert at processing and analyzing large documents (best-effort 256K context window).
845
+ const systemPrompt = `You are Kimi K2.7-Code, expert at processing and analyzing large documents (best-effort 262K context window).
842
846
  Task: ${taskPrompts[args.task || "analyze"]}
843
847
  Format: ${formatPrompts[args.outputFormat || "detailed"]}
844
848
  ${args.query ? `Specific query: ${args.query}` : ''}
@@ -853,7 +857,7 @@ ${FORMAT_INSTRUCTION}`;
853
857
  { role: "user", content: args.content + fileContext }
854
858
  ];
855
859
  const reportFn = reportProgress ?? (async () => { });
856
- return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_6, 0.2, 8000), reportFn, 300000);
860
+ return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_7_CODE, 0.2, 8000), reportFn, 300000);
857
861
  }
858
862
  });
859
863
  /**
@@ -1151,12 +1155,12 @@ ${FORMAT_INSTRUCTION}`;
1151
1155
  };
1152
1156
  /**
1153
1157
  * GLM Reason Tool
1154
- * Agentic reasoning & tool-use planning with Zhipu GLM-5.1 (SWE-Bench Pro leader).
1158
+ * Agentic reasoning & tool-use planning with Zhipu GLM-5.2 (1M ctx, long-horizon coding leader).
1155
1159
  * Best for: agentic task planning, tool-use strategy, software-engineering reasoning.
1156
1160
  */
1157
1161
  export const glmReasonTool = {
1158
1162
  name: "glm_reason",
1159
- description: "Agentic reasoning & tool-use planning with Zhipu GLM-5.1 (SWE-Bench Pro leader). Put your PROBLEM in the 'problem' parameter.",
1163
+ description: "Agentic reasoning & tool-use planning with Zhipu GLM-5.2 (1M ctx, top open-weights for long-horizon coding). Put your PROBLEM in the 'problem' parameter.",
1160
1164
  parameters: z.object({
1161
1165
  problem: z.string().describe("The problem to reason about (REQUIRED - put your question here)"),
1162
1166
  context: z.string().optional().describe("Additional context for the reasoning task"),
@@ -1179,7 +1183,7 @@ export const glmReasonTool = {
1179
1183
  const messages = [
1180
1184
  {
1181
1185
  role: "system",
1182
- content: `You are Zhipu GLM-5.1, a frontier agentic model and SWE-Bench Pro leader, strong at tool-use planning and software-engineering reasoning.
1186
+ content: `You are Zhipu GLM-5.2, a frontier open-weights agentic model with a 1M-token context, strong at long-horizon coding, tool-use planning and software-engineering reasoning.
1183
1187
  ${approachPrompts[args.approach || 'agentic']}.
1184
1188
  State your plan, reason through it, then give a decisive conclusion. Flag assumptions and risks explicitly.
1185
1189
  ${args.context ? `Context: ${args.context}` : ''}
@@ -1191,7 +1195,7 @@ ${FORMAT_INSTRUCTION}`
1191
1195
  }
1192
1196
  ];
1193
1197
  const reportFn = reportProgress ?? (async () => { });
1194
- return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.GLM_5_1, 0.3, 8000), reportFn);
1198
+ return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.GLM_5_2, 0.3, 8000), reportFn);
1195
1199
  }
1196
1200
  };
1197
1201
  /**
@@ -1308,7 +1312,7 @@ export function getAllOpenRouterTools() {
1308
1312
  // NEW tools (Jun 2026)
1309
1313
  deepseekReasonTool, // DeepSeek V4 Pro - frontier reasoning/math (open-weight)
1310
1314
  deepseekAlgoTool, // DeepSeek V4 Pro - algorithmic code review (top AIME/CodeElo)
1311
- glmReasonTool, // Zhipu GLM-5.1 - agentic reasoning (SWE-Bench Pro leader)
1315
+ glmReasonTool, // Zhipu GLM-5.2 - agentic reasoning (1M ctx, long-horizon coding)
1312
1316
  stepfunReasonTool, // StepFun Step 3.7 Flash - efficient reasoning
1313
1317
  ernieReasonTool, // Baidu ERNIE 4.5 VL - broad-knowledge reasoning
1314
1318
  ];
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "tachibot-mcp",
3
3
  "mcpName": "io.github.byPawel/tachibot-mcp",
4
4
  "displayName": "TachiBot MCP - Universal AI Orchestrator",
5
- "version": "2.23.1",
5
+ "version": "2.23.3",
6
6
  "type": "module",
7
7
  "main": "dist/src/server.js",
8
8
  "bin": {