tiger-agent 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./IMG_5745.jpg" alt="Tiger bot" width="900" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# 🐯 Tiger Agent
|
|
2
6
|
|
|
3
7
|
[](https://www.npmjs.com/package/tiger-agent)
|
|
@@ -10,13 +14,20 @@ Made by **AI Research Group, Department of Civil Engineering, KMUTT**
|
|
|
10
14
|
|
|
11
15
|
---
|
|
12
16
|
|
|
13
|
-
## 🆕 What's New — v0.2.
|
|
17
|
+
## 🆕 What's New — v0.2.4
|
|
18
|
+
|
|
19
|
+
- **ClawHub skill install fixed** — `clawhub_install` and `clawhub_search` now work correctly when installed via `npm install -g`
|
|
20
|
+
- **No required API keys** — `tiger onboard` skips providers with no key; any single provider is enough to start
|
|
21
|
+
- **`/limit` Telegram command** — set per-provider daily token limits from chat without restarting
|
|
22
|
+
- **Soul & ownskill refresh fixed** — 24-hour regeneration timer now uses DB timestamps, not file mtime, so reflection appends no longer block the update cycle
|
|
23
|
+
|
|
24
|
+
### v0.2.0
|
|
14
25
|
|
|
15
26
|
- **npm global install** — `npm install -g tiger-agent`, no git clone needed
|
|
16
27
|
- **Multi-provider LLM** — 5 providers (Kimi, Z.ai, MiniMax, Claude, Moonshot) with auto-fallback
|
|
17
28
|
- **Daily token limits** — per-provider limits with automatic switching at UTC midnight
|
|
18
29
|
- **`tiger` CLI** — unified command: `tiger onboard`, `tiger start`, `tiger telegram`, `tiger stop`
|
|
19
|
-
- **Telegram `/api
|
|
30
|
+
- **Telegram `/api`, `/tokens`, `/limit`** — manage providers and usage from chat
|
|
20
31
|
- **Encrypted secrets** — optional at-rest encryption for API keys
|
|
21
32
|
|
|
22
33
|
---
|
|
@@ -259,6 +270,27 @@ Without it, Tiger falls back to cosine similarity in Python — slower but fully
|
|
|
259
270
|
| **Skills** | `list_skills`, `load_skill`, `clawhub_search`, `clawhub_install` |
|
|
260
271
|
| **Orchestration** | `run_sub_agents` |
|
|
261
272
|
|
|
273
|
+
### ClawHub Skills
|
|
274
|
+
|
|
275
|
+
Tiger can search and install skills from [ClawHub](https://clawhub.dev) — a community registry of reusable agent skills. The `clawhub` CLI is bundled with Tiger, no separate install needed.
|
|
276
|
+
|
|
277
|
+
Enable skill install in `~/.tiger/.env`:
|
|
278
|
+
|
|
279
|
+
```env
|
|
280
|
+
ALLOW_SKILL_INSTALL=true
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Then just ask Tiger in chat:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
Search for a web search skill on clawhub
|
|
287
|
+
Install the web-search skill
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Skills are installed to `~/.tiger/skills/` and loaded automatically on demand.
|
|
291
|
+
|
|
292
|
+
> **Note:** `ALLOW_SKILL_INSTALL=true` must be set during `tiger onboard` or added manually to `~/.tiger/.env`.
|
|
293
|
+
|
|
262
294
|
---
|
|
263
295
|
|
|
264
296
|
## 🔒 Security
|
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function getLegacyRootMirrorPath(filePath) {
|
|
5
|
+
const canonical = path.resolve(filePath);
|
|
6
|
+
const candidate = path.resolve(process.cwd(), path.basename(canonical));
|
|
7
|
+
|
|
8
|
+
if (candidate === canonical) return null;
|
|
9
|
+
if (!fs.existsSync(candidate)) return null;
|
|
10
|
+
|
|
11
|
+
return candidate;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function syncLegacyRootMirror(filePath) {
|
|
15
|
+
const canonical = path.resolve(filePath);
|
|
16
|
+
const legacy = getLegacyRootMirrorPath(canonical);
|
|
17
|
+
if (!legacy) return;
|
|
18
|
+
|
|
19
|
+
const content = fs.readFileSync(canonical, 'utf8');
|
|
20
|
+
fs.writeFileSync(legacy, content, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeContextFile(filePath, content) {
|
|
24
|
+
const canonical = path.resolve(filePath);
|
|
25
|
+
fs.writeFileSync(canonical, content, 'utf8');
|
|
26
|
+
syncLegacyRootMirror(canonical);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function appendContextFile(filePath, content) {
|
|
30
|
+
const canonical = path.resolve(filePath);
|
|
31
|
+
fs.appendFileSync(canonical, content, 'utf8');
|
|
32
|
+
syncLegacyRootMirror(canonical);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
writeContextFile,
|
|
37
|
+
appendContextFile,
|
|
38
|
+
syncLegacyRootMirror
|
|
39
|
+
};
|
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { dataDir } = require('../config');
|
|
4
4
|
const { ensureDir } = require('../utils');
|
|
5
|
+
const { writeContextFile, syncLegacyRootMirror } = require('./contextFileMirrors');
|
|
5
6
|
|
|
6
7
|
const files = ['soul.md', 'human.md', 'human2.md', 'ownskill.md'];
|
|
7
8
|
|
|
@@ -10,8 +11,10 @@ function ensureContextFiles() {
|
|
|
10
11
|
for (const name of files) {
|
|
11
12
|
const full = path.join(dataDir, name);
|
|
12
13
|
if (!fs.existsSync(full)) {
|
|
13
|
-
|
|
14
|
+
writeContextFile(full, `# ${name.replace('.md', '')}\n\n`);
|
|
15
|
+
continue;
|
|
14
16
|
}
|
|
17
|
+
syncLegacyRootMirror(full);
|
|
15
18
|
}
|
|
16
19
|
}
|
|
17
20
|
|
package/src/agent/mainAgent.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
memoryIngestMinChars
|
|
13
13
|
} = require('../config');
|
|
14
14
|
const { loadContextFiles } = require('./contextFiles');
|
|
15
|
+
const { writeContextFile, appendContextFile } = require('./contextFileMirrors');
|
|
15
16
|
const { tools, callTool } = require('./toolbox');
|
|
16
17
|
const {
|
|
17
18
|
ensureConversation,
|
|
@@ -98,7 +99,7 @@ async function maybeUpdateHumanFile(userText, assistantText) {
|
|
|
98
99
|
if (!append) return;
|
|
99
100
|
|
|
100
101
|
const block = `\n## Update ${new Date().toISOString()}\n${append}\n`;
|
|
101
|
-
|
|
102
|
+
appendContextFile(path.resolve(human2.full), block);
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
function buildSystemPrompt(contextText, memoriesText) {
|
|
@@ -173,7 +174,7 @@ async function maybeUpdateOwnSkillSummary(conversationIdValue) {
|
|
|
173
174
|
|
|
174
175
|
const next = String(message.content || '').trim();
|
|
175
176
|
if (!next) return;
|
|
176
|
-
|
|
177
|
+
writeContextFile(path.resolve(ownSkillPath), `${next}\n`);
|
|
177
178
|
setMeta(OWNSKILL_META_KEY, Date.now());
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -209,7 +210,7 @@ async function maybeUpdateSoulSummary(conversationIdValue) {
|
|
|
209
210
|
|
|
210
211
|
const next = String(message.content || '').trim();
|
|
211
212
|
if (!next) return;
|
|
212
|
-
|
|
213
|
+
writeContextFile(path.resolve(soulPath), `${next}\n`);
|
|
213
214
|
setMeta(SOUL_META_KEY, Date.now());
|
|
214
215
|
}
|
|
215
216
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { chatCompletion, embedText } = require('../llmClient');
|
|
4
|
-
const { dataDir, embeddingsEnabled } = require('../config');
|
|
4
|
+
const { dataDir, embeddingsEnabled, reflectionUpdateHours } = require('../config');
|
|
5
5
|
const { addMemory, getMeta, setMeta, getMessagesSince, getRecentMessagesAll } = require('./db');
|
|
6
|
+
const { writeContextFile } = require('./contextFileMirrors');
|
|
6
7
|
|
|
7
8
|
const REFLECTION_META_KEY = 'memory_reflection_last_run_ts';
|
|
8
9
|
const MAX_MESSAGE_SCAN = 600;
|
|
@@ -67,7 +68,7 @@ function appendTimestampedBullets(filePath, heading, bullets, stamp) {
|
|
|
67
68
|
const withHeading = ensureHeading(existing, heading);
|
|
68
69
|
const lines = bullets.map((line) => `- [${stamp}] ${line}`).join('\n');
|
|
69
70
|
const next = `${withHeading.trimEnd()}\n${lines}\n`;
|
|
70
|
-
|
|
71
|
+
writeContextFile(full, next);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
function appendHuman2Update(filePath, payload, stampIso) {
|
|
@@ -80,7 +81,7 @@ function appendHuman2Update(filePath, payload, stampIso) {
|
|
|
80
81
|
for (const w of payload.successfulWorkflows) lines.push(`- Workflow: ${w}`);
|
|
81
82
|
if (!lines.length) return;
|
|
82
83
|
const block = `\n## Update ${stampIso}\n${lines.join('\n')}\n`;
|
|
83
|
-
|
|
84
|
+
writeContextFile(full, `${existing.trimEnd()}${block}`);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
async function generateReflection(rows, sinceIso, untilIso) {
|
|
@@ -123,9 +124,20 @@ async function generateReflection(rows, sinceIso, untilIso) {
|
|
|
123
124
|
};
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
function shouldRunReflectionCycle(lastRunTs) {
|
|
128
|
+
if (!lastRunTs) return true;
|
|
129
|
+
const hoursPassed = (nowTs() - lastRunTs) / (60 * 60 * 1000);
|
|
130
|
+
return hoursPassed >= reflectionUpdateHours;
|
|
131
|
+
}
|
|
132
|
+
|
|
126
133
|
async function maybeRunReflectionCycle({ force = false } = {}) {
|
|
127
134
|
const startedAt = nowTs();
|
|
128
135
|
const lastRunTs = Number(getMeta(REFLECTION_META_KEY, 0) || 0);
|
|
136
|
+
|
|
137
|
+
if (!force && !shouldRunReflectionCycle(lastRunTs)) {
|
|
138
|
+
return { ok: true, skipped: true, reason: 'not_yet_due' };
|
|
139
|
+
}
|
|
140
|
+
|
|
129
141
|
const rows = lastRunTs
|
|
130
142
|
? getMessagesSince(lastRunTs, MAX_MESSAGE_SCAN)
|
|
131
143
|
: getRecentMessagesAll(Math.min(MAX_MESSAGE_SCAN, 240));
|
|
@@ -185,6 +197,7 @@ async function maybeRunReflectionCycle({ force = false } = {}) {
|
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
setMeta(REFLECTION_META_KEY, startedAt);
|
|
200
|
+
console.log(`[ReflectionCycle] Completed at ${stampIso} (${rows.length} messages processed)`);
|
|
188
201
|
return { ok: true, skipped: false, at: stampIso };
|
|
189
202
|
}
|
|
190
203
|
|
|
@@ -8,14 +8,30 @@ function startReflectionScheduler() {
|
|
|
8
8
|
|
|
9
9
|
const everyMs = Math.max(1, Number(reflectionUpdateHours || 12)) * 60 * 60 * 1000;
|
|
10
10
|
|
|
11
|
+
console.log(`[ReflectionScheduler] Starting with interval: ${reflectionUpdateHours || 12} hours (${everyMs}ms)`);
|
|
12
|
+
|
|
11
13
|
// Kick one asynchronous check on startup.
|
|
12
|
-
maybeRunReflectionCycle().catch(() => {
|
|
14
|
+
maybeRunReflectionCycle().catch((err) => {
|
|
15
|
+
console.error('[ReflectionScheduler] Startup check failed:', err.message);
|
|
16
|
+
});
|
|
13
17
|
|
|
14
18
|
intervalHandle = setInterval(() => {
|
|
15
|
-
|
|
19
|
+
console.log('[ReflectionScheduler] Running scheduled reflection cycle...');
|
|
20
|
+
maybeRunReflectionCycle().catch((err) => {
|
|
21
|
+
console.error('[ReflectionScheduler] Scheduled cycle failed:', err.message);
|
|
22
|
+
});
|
|
16
23
|
}, everyMs);
|
|
17
24
|
}
|
|
18
25
|
|
|
26
|
+
function stopReflectionScheduler() {
|
|
27
|
+
if (intervalHandle) {
|
|
28
|
+
clearInterval(intervalHandle);
|
|
29
|
+
intervalHandle = null;
|
|
30
|
+
console.log('[ReflectionScheduler] Stopped');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
19
34
|
module.exports = {
|
|
20
|
-
startReflectionScheduler
|
|
35
|
+
startReflectionScheduler,
|
|
36
|
+
stopReflectionScheduler
|
|
21
37
|
};
|