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 +13 -0
- package/README.md +2 -2
- package/dist/src/config/model-constants.js +10 -6
- package/dist/src/config/model-defaults.js +1 -1
- package/dist/src/config/timeout-config.js +2 -2
- package/dist/src/memory/index.js +1 -1
- package/dist/src/memory/memory-config.js +2 -3
- package/dist/src/memory/memory-manager.js +10 -4
- package/dist/src/memory/providers/dokoro-provider.js +380 -276
- package/dist/src/tools/jury-tool.js +5 -5
- package/dist/src/tools/openrouter-tools.js +28 -24
- package/package.json +1 -1
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
|
-
[](https://www.npmjs.com/package/tachibot-mcp)
|
|
8
8
|
[](#-tool-ecosystem-57-tools)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](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.
|
|
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.
|
|
91
|
-
// K2.
|
|
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
|
-
|
|
95
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
40
|
-
*
|
|
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
|
package/dist/src/memory/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
*
|
|
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.
|
|
14
|
-
this.
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 (!
|
|
55
|
-
|
|
65
|
+
if (!item.id) {
|
|
66
|
+
item.id = this.generateId();
|
|
56
67
|
}
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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.
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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('
|
|
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.
|
|
162
|
-
|
|
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
|
|
166
|
-
if (
|
|
167
|
-
|
|
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
|
|
156
|
+
return true;
|
|
170
157
|
}
|
|
171
158
|
catch (error) {
|
|
172
|
-
console.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
|
-
|
|
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 (
|
|
197
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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
|
-
|
|
231
|
-
await this.client.getStats();
|
|
291
|
+
await fs.access(filePath);
|
|
232
292
|
return true;
|
|
233
293
|
}
|
|
234
|
-
catch
|
|
294
|
+
catch {
|
|
235
295
|
return false;
|
|
236
296
|
}
|
|
237
297
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
244
|
-
|
|
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
|
-
*
|
|
330
|
+
* Resolve a stored id (file basename without .md) back to its path.
|
|
248
331
|
*/
|
|
249
|
-
async
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
322
|
-
|
|
356
|
+
catch {
|
|
357
|
+
frontmatter = {};
|
|
323
358
|
}
|
|
324
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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:
|
|
356
|
-
content:
|
|
384
|
+
id: path.basename(filePath, '.md'),
|
|
385
|
+
content: body.trim(),
|
|
357
386
|
tier,
|
|
358
|
-
userId:
|
|
359
|
-
projectId:
|
|
360
|
-
teamId:
|
|
361
|
-
timestamp
|
|
362
|
-
metadata
|
|
363
|
-
tags:
|
|
364
|
-
ttl:
|
|
365
|
-
accessCount:
|
|
366
|
-
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 /
|
|
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.
|
|
542
|
-
*
|
|
543
|
-
* $0.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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