tiger-agent 0.2.5 → 0.3.1
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/.env.example +10 -0
- package/README.md +260 -4
- package/package.json +1 -1
- package/src/agent/skills.js +1 -1
- package/src/apiProviders.js +8 -9
- package/src/apiProviders.js.bak +222 -0
- package/src/cli.js +4 -0
- package/src/config.js +11 -0
- package/src/llmClient.js +11 -1
- package/src/swarm/agentRuntime.js +699 -0
- package/src/swarm/agentRuntime.js.bak +456 -0
- package/src/swarm/configStore.js +360 -0
- package/src/swarm/index.js +25 -0
- package/src/swarm/taskBus.js +246 -0
- package/src/telegram/bot.js +329 -1
package/.env.example
CHANGED
|
@@ -10,6 +10,16 @@ GEMINI_API_KEY=your_gemini_api_key_here
|
|
|
10
10
|
# Telegram Bot
|
|
11
11
|
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
|
|
12
12
|
TELEGRAM_CHAT_ID=8172556270
|
|
13
|
+
# Swarm agent step timeout in ms (0 = no extra swarm timeout)
|
|
14
|
+
SWARM_AGENT_TIMEOUT_MS=0
|
|
15
|
+
# Swarm-only provider failover on timeout/network/API error (true/false)
|
|
16
|
+
SWARM_ROUTE_ON_PROVIDER_ERROR=false
|
|
17
|
+
# Swarm default flow for new Telegram tasks (auto|design|research_build)
|
|
18
|
+
SWARM_DEFAULT_FLOW=auto
|
|
19
|
+
# First agent policy (auto|flow|fixed|designer|scout|coder|critic|senior_eng|spec_writer)
|
|
20
|
+
SWARM_FIRST_AGENT_POLICY=auto
|
|
21
|
+
# Used when SWARM_FIRST_AGENT_POLICY=fixed
|
|
22
|
+
SWARM_FIRST_AGENT=designer
|
|
13
23
|
|
|
14
24
|
# X/Twitter (optional - paid API)
|
|
15
25
|
X_BEARER_TOKEN=your_x_bearer_token_here
|
package/README.md
CHANGED
|
@@ -8,13 +8,25 @@
|
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Agentic Swarm AI Agent** with persistent long-term memory, multi-provider LLM support, token management, self-learning, and Telegram bot integration — designed for 24/7 autonomous operation on Linux.
|
|
12
12
|
|
|
13
13
|
Made by **AI Research Group, Department of Civil Engineering, KMUTT**
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
## 🆕 What's New — v0.
|
|
17
|
+
## 🆕 What's New — v0.3.1
|
|
18
|
+
|
|
19
|
+
- **YAML swarm architecture** — swarm flow is now configurable in `swarm/architecture/*.yaml` with orchestrator, agents, stages, and judgment matrix
|
|
20
|
+
- **YAML task style** — task routing style is configurable in `tasks/styles/*.yaml` and can select which architecture file to use
|
|
21
|
+
- **Telegram architecture editing** — added `/architecture` and `/taskstyle` commands so Telegram users can list/show/write YAML and switch default architecture
|
|
22
|
+
- **Parallel design orchestration default** — default architecture runs 3 designers in parallel, reviewer selects best, revision loop applies, then spec writer finalizes
|
|
23
|
+
|
|
24
|
+
### v0.2.5
|
|
25
|
+
|
|
26
|
+
- **Context-file mirror compatibility** — if legacy root files like `./soul.md` or `./ownskill.md` already exist, Tiger now mirrors updates to them automatically while continuing to use `DATA_DIR` as the canonical source
|
|
27
|
+
- **README path clarification** — docs now explicitly distinguish canonical `DATA_DIR` files from optional legacy root mirrors
|
|
28
|
+
|
|
29
|
+
### v0.2.4
|
|
18
30
|
|
|
19
31
|
- **ClawHub skill install fixed** — `clawhub_install` and `clawhub_search` now work correctly when installed via `npm install -g`
|
|
20
32
|
- **No required API keys** — `tiger onboard` skips providers with no key; any single provider is enough to start
|
|
@@ -100,6 +112,13 @@ tiger status # check if running
|
|
|
100
112
|
tiger stop # stop
|
|
101
113
|
```
|
|
102
114
|
|
|
115
|
+
**Restart background bot (after editing `.env` in this repo):**
|
|
116
|
+
```bash
|
|
117
|
+
cd /root/tiger
|
|
118
|
+
npm run telegram:stop
|
|
119
|
+
npm run telegram:bg
|
|
120
|
+
```
|
|
121
|
+
|
|
103
122
|
Logs: `~/.tiger/logs/telegram.out.log`
|
|
104
123
|
|
|
105
124
|
---
|
|
@@ -145,7 +164,7 @@ Logs: `~/.tiger/logs/telegram.out.log`
|
|
|
145
164
|
| `ALLOW_SHELL` | `false` | Enable shell tool |
|
|
146
165
|
| `ALLOW_SKILL_INSTALL` | `false` | Enable ClawHub skill install |
|
|
147
166
|
| `VECTOR_DB_PATH` | `~/.tiger/db/memory.sqlite` | SQLite vector DB path |
|
|
148
|
-
| `DATA_DIR` | `~/.tiger/data` |
|
|
167
|
+
| `DATA_DIR` | `~/.tiger/data` | Canonical context files directory |
|
|
149
168
|
| `OWN_SKILL_UPDATE_HOURS` | `24` | Hours between `ownskill.md` regenerations (min 1) |
|
|
150
169
|
| `SOUL_UPDATE_HOURS` | `24` | Hours between `soul.md` regenerations (min 1) |
|
|
151
170
|
| `REFLECTION_UPDATE_HOURS` | `12` | Hours between reflection cycles (min 1) |
|
|
@@ -188,6 +207,22 @@ ZAI_TOKEN_LIMIT=100000
|
|
|
188
207
|
MINIMAX_TOKEN_LIMIT=100000
|
|
189
208
|
CLAUDE_TOKEN_LIMIT=500000
|
|
190
209
|
MOONSHOT_TOKEN_LIMIT=100000
|
|
210
|
+
|
|
211
|
+
# Provider request timeouts (ms)
|
|
212
|
+
KIMI_TIMEOUT_MS=120000
|
|
213
|
+
ZAI_TIMEOUT_MS=120000
|
|
214
|
+
|
|
215
|
+
# Swarm worker-step timeout (0 = no extra swarm timeout)
|
|
216
|
+
SWARM_AGENT_TIMEOUT_MS=120000
|
|
217
|
+
|
|
218
|
+
# Swarm only: on timeout/network/API error, retry via next provider
|
|
219
|
+
SWARM_ROUTE_ON_PROVIDER_ERROR=true
|
|
220
|
+
|
|
221
|
+
# Swarm task entry policy
|
|
222
|
+
SWARM_DEFAULT_FLOW=auto
|
|
223
|
+
SWARM_FIRST_AGENT_POLICY=auto
|
|
224
|
+
# Used only when SWARM_FIRST_AGENT_POLICY=fixed
|
|
225
|
+
SWARM_FIRST_AGENT=designer
|
|
191
226
|
```
|
|
192
227
|
|
|
193
228
|
### Auto-Switch Behaviour
|
|
@@ -209,15 +244,232 @@ MOONSHOT_TOKEN_LIMIT=100000
|
|
|
209
244
|
| `/tokens` | Show today's token usage per provider |
|
|
210
245
|
| `/limit` | Show daily token limits per provider |
|
|
211
246
|
| `/limit <provider> <n>` | Set daily token limit (0 = unlimited, e.g. `/limit zai 100000`) |
|
|
247
|
+
| `/swarm` | Show agent swarm status (ON/OFF) |
|
|
248
|
+
| `/swarm <on|off>` | Enable or disable internal swarm routing for normal messages |
|
|
249
|
+
| `/status` | Show swarm task queues (`pending`, `in_progress`, `done`, `failed`) |
|
|
250
|
+
| `/task` | List swarm tasks across queues |
|
|
251
|
+
| `/task continue <task_id>` | Resume a failed/stuck swarm task from the last failed agent |
|
|
252
|
+
| `/task retry <task_id>` | Alias of `/task continue <task_id>` |
|
|
253
|
+
| `/task delete <task_id>` | Delete a swarm task file from queue storage |
|
|
254
|
+
| `/agents` | Show internal swarm agents and availability |
|
|
255
|
+
| `/cancel <task_id>` | Cancel a swarm task |
|
|
256
|
+
| `/ask <agent> <question>` | Ask a specific internal agent role directly |
|
|
257
|
+
| `/architecture` | List swarm architecture YAML files |
|
|
258
|
+
| `/architecture show <file>` | Show one architecture YAML file |
|
|
259
|
+
| `/architecture use <file>` | Set default task-style architecture file |
|
|
260
|
+
| `/architecture write <file>` + newline + yaml | Save architecture YAML from Telegram |
|
|
261
|
+
| `/taskstyle` | List task-style YAML files |
|
|
262
|
+
| `/taskstyle show <file>` | Show one task-style YAML file |
|
|
263
|
+
| `/taskstyle write <file>` + newline + yaml | Save task-style YAML from Telegram |
|
|
212
264
|
| `/help` | Show all commands |
|
|
213
265
|
|
|
266
|
+
### Swarm Settings (`/swarm`)
|
|
267
|
+
|
|
268
|
+
Tiger v0.3.1 includes an internal agent swarm for Telegram message routing.
|
|
269
|
+
|
|
270
|
+
- **Default:** swarm is **ON** when the Telegram bot starts
|
|
271
|
+
- **`/swarm on`**: regular user messages are routed through the YAML architecture in `swarm/architecture/*.yaml` (selected by `tasks/styles/default.yaml`)
|
|
272
|
+
- **`/swarm off`**: regular user messages skip the swarm and go directly to the standard Tiger agent reply path
|
|
273
|
+
- **Scope:** this toggle affects only **normal chat messages** (not admin commands like `/api`, `/tokens`, `/limit`)
|
|
274
|
+
- **Current persistence:** the `/swarm` toggle is currently **in-memory only** and resets to **ON** after bot restart
|
|
275
|
+
- **Task resume:** use `/task continue <task_id>` (or `/task retry <task_id>`) to continue a failed timeout/API-error task without starting over
|
|
276
|
+
|
|
277
|
+
### Swarm Timeout / Failover (`.env`)
|
|
278
|
+
|
|
279
|
+
- `SWARM_AGENT_TIMEOUT_MS`: timeout per swarm worker step (e.g. one `designer` turn). `0` disables the extra swarm timeout.
|
|
280
|
+
- `SWARM_ROUTE_ON_PROVIDER_ERROR=true|false`: swarm-only provider failover on timeout/network/API errors.
|
|
281
|
+
- Provider timeouts are separate and provider-specific, for example `KIMI_TIMEOUT_MS`, `ZAI_TIMEOUT_MS`, `CLAUDE_TIMEOUT_MS`.
|
|
282
|
+
|
|
283
|
+
### Swarm Entry Policy (`.env`)
|
|
284
|
+
|
|
285
|
+
- `SWARM_DEFAULT_FLOW=auto|design|research_build`: default flow for new Telegram swarm tasks.
|
|
286
|
+
- `SWARM_FIRST_AGENT_POLICY` controls who starts first:
|
|
287
|
+
- `auto` (default): Tiger/orchestrator picks based on the goal text
|
|
288
|
+
- `flow`: use flow mapping (`research_build -> scout`, otherwise `designer`)
|
|
289
|
+
- `fixed`: use `SWARM_FIRST_AGENT`
|
|
290
|
+
- or set a direct agent name (for example `designer`, `scout`, `coder`)
|
|
291
|
+
- `SWARM_FIRST_AGENT` is used when `SWARM_FIRST_AGENT_POLICY=fixed`
|
|
292
|
+
|
|
293
|
+
Examples:
|
|
294
|
+
|
|
295
|
+
```text
|
|
296
|
+
/swarm
|
|
297
|
+
/swarm off
|
|
298
|
+
/swarm on
|
|
299
|
+
/task
|
|
300
|
+
/task retry task_xxx
|
|
301
|
+
/task delete task_xxx
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Swarm Agent Files (Manual Customization)
|
|
305
|
+
|
|
306
|
+
Tiger creates a local swarm workspace so you can manually customize each agent's behavior.
|
|
307
|
+
|
|
308
|
+
Default folders (project/runtime root):
|
|
309
|
+
|
|
310
|
+
```text
|
|
311
|
+
agents/
|
|
312
|
+
tiger/
|
|
313
|
+
designer/
|
|
314
|
+
senior_eng/
|
|
315
|
+
spec_writer/
|
|
316
|
+
scout/
|
|
317
|
+
coder/
|
|
318
|
+
critic/
|
|
319
|
+
tasks/
|
|
320
|
+
pending/
|
|
321
|
+
in_progress/
|
|
322
|
+
done/
|
|
323
|
+
failed/
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Each agent folder includes files such as:
|
|
327
|
+
|
|
328
|
+
- `soul.md` — the agent's personality, rules, and mindset
|
|
329
|
+
- `ownskill.md` — what the agent is good at / preferred workflow
|
|
330
|
+
- `experience.json` — learned lessons and task stats
|
|
331
|
+
- `memory.md` — long-form notes/patterns
|
|
332
|
+
- `human.md` — only for `agents/tiger/` (user preferences)
|
|
333
|
+
|
|
334
|
+
Manual setup / editing:
|
|
335
|
+
|
|
336
|
+
- Start the bot once (`tiger telegram` or `tiger start`) and Tiger will auto-create missing `agents/` and `tasks/` folders
|
|
337
|
+
- You can then open and edit files like `agents/designer/soul.md` or `agents/senior_eng/soul.md` manually
|
|
338
|
+
- Your edits are used on future swarm runs (for example `/ask designer ...` or normal swarm-routed messages)
|
|
339
|
+
- Keep edits in plain Markdown/JSON and avoid deleting required files while the bot is running
|
|
340
|
+
|
|
341
|
+
Example customization ideas:
|
|
342
|
+
|
|
343
|
+
- Make `designer` more creative / visual
|
|
344
|
+
- Make `senior_eng` stricter about security, error handling, and scalability
|
|
345
|
+
- Make `spec_writer` produce a specific document format your team uses
|
|
346
|
+
|
|
347
|
+
### Swarm Architecture YAML (v0.3.1)
|
|
348
|
+
|
|
349
|
+
Default files:
|
|
350
|
+
|
|
351
|
+
```text
|
|
352
|
+
swarm/architecture/tiger_parallel_design.yaml
|
|
353
|
+
tasks/styles/default.yaml
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Default architecture behavior:
|
|
357
|
+
|
|
358
|
+
- Orchestrator: `tiger`
|
|
359
|
+
- Stage 1: send task simultaneously to `designer_a`, `designer_b`, `designer_c` (different souls/personalities)
|
|
360
|
+
- `designer_a`: senior conservative
|
|
361
|
+
- `designer_b`: balanced, around 40 style
|
|
362
|
+
- `designer_c`: young aggressive, higher risk appetite
|
|
363
|
+
- Stage 2: `reviewer` evaluates with the judgment matrix and picks best candidate
|
|
364
|
+
- Stage 3: selected designer revises based on reviewer feedback (loop until approved)
|
|
365
|
+
- Stage 4: `spec_writer` writes final output in two sections: **Calculation Report** and **Executive Summary**
|
|
366
|
+
|
|
367
|
+
Example `swarm/architecture/tiger_parallel_design.yaml`:
|
|
368
|
+
|
|
369
|
+
```yaml
|
|
370
|
+
version: 1
|
|
371
|
+
name: tiger_parallel_design
|
|
372
|
+
main_orchestrator: tiger
|
|
373
|
+
start_stage: design_parallel
|
|
374
|
+
agents:
|
|
375
|
+
- id: designer_a
|
|
376
|
+
runtime_agent: designer_a
|
|
377
|
+
role: designer
|
|
378
|
+
- id: designer_b
|
|
379
|
+
runtime_agent: designer_b
|
|
380
|
+
role: designer
|
|
381
|
+
- id: designer_c
|
|
382
|
+
runtime_agent: designer_c
|
|
383
|
+
role: designer
|
|
384
|
+
- id: reviewer
|
|
385
|
+
runtime_agent: senior_eng
|
|
386
|
+
role: reviewer
|
|
387
|
+
- id: spec_writer
|
|
388
|
+
runtime_agent: spec_writer
|
|
389
|
+
role: spec_writer
|
|
390
|
+
stages:
|
|
391
|
+
- id: design_parallel
|
|
392
|
+
type: parallel
|
|
393
|
+
roles:
|
|
394
|
+
- designer_a
|
|
395
|
+
- designer_b
|
|
396
|
+
- designer_c
|
|
397
|
+
store_as: design_candidates
|
|
398
|
+
next: review_best
|
|
399
|
+
- id: review_best
|
|
400
|
+
type: judge
|
|
401
|
+
role: reviewer
|
|
402
|
+
candidates_from: design_candidates
|
|
403
|
+
selected_role_key: selected_role
|
|
404
|
+
feedback_key: reviewer_feedback
|
|
405
|
+
calculation_report_key: best_calculation_report
|
|
406
|
+
pass_next: final_spec
|
|
407
|
+
fail_next: revise_selected
|
|
408
|
+
- id: revise_selected
|
|
409
|
+
type: revise
|
|
410
|
+
role_from_context: selected_role
|
|
411
|
+
feedback_from_context: reviewer_feedback
|
|
412
|
+
candidates_from: design_candidates
|
|
413
|
+
update_context_keys_from_revised:
|
|
414
|
+
- best_calculation_report
|
|
415
|
+
next: review_best
|
|
416
|
+
- id: final_spec
|
|
417
|
+
type: final
|
|
418
|
+
role: spec_writer
|
|
419
|
+
source_from_context: best_calculation_report
|
|
420
|
+
output_sections:
|
|
421
|
+
- Calculation Report
|
|
422
|
+
- Executive Summary
|
|
423
|
+
output_notes: Include formulas, assumptions, step-by-step calculations, final values, and concise recommendations.
|
|
424
|
+
next: tiger_done
|
|
425
|
+
judgment_matrix:
|
|
426
|
+
criteria:
|
|
427
|
+
- name: objective_fit
|
|
428
|
+
weight: 0.35
|
|
429
|
+
description: How well the design satisfies the objective.
|
|
430
|
+
- name: feasibility
|
|
431
|
+
weight: 0.25
|
|
432
|
+
description: Delivery realism and technical viability.
|
|
433
|
+
- name: clarity
|
|
434
|
+
weight: 0.2
|
|
435
|
+
description: Readability and implementation clarity.
|
|
436
|
+
- name: risk
|
|
437
|
+
weight: 0.2
|
|
438
|
+
description: Risk exposure and mitigation quality.
|
|
439
|
+
pass_rule: reviewer_approval
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Task Style YAML
|
|
443
|
+
|
|
444
|
+
Task style is the selector/policy layer for swarm execution.
|
|
445
|
+
|
|
446
|
+
- `architecture`: which file in `swarm/architecture/` to run
|
|
447
|
+
- `flow`: flow label for task routing mode
|
|
448
|
+
- `objective_prefix`: text prepended to the user objective before processing
|
|
449
|
+
|
|
450
|
+
Default file:
|
|
451
|
+
|
|
452
|
+
```text
|
|
453
|
+
tasks/styles/default.yaml
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Example:
|
|
457
|
+
|
|
458
|
+
```yaml
|
|
459
|
+
version: 1
|
|
460
|
+
name: default
|
|
461
|
+
architecture: tiger_parallel_design.yaml
|
|
462
|
+
flow: architecture
|
|
463
|
+
objective_prefix: "Objective:"
|
|
464
|
+
```
|
|
465
|
+
|
|
214
466
|
---
|
|
215
467
|
|
|
216
468
|
## 🧠 Memory & Context
|
|
217
469
|
|
|
218
470
|
### Context Files
|
|
219
471
|
|
|
220
|
-
Loaded on every turn from `~/.tiger/data
|
|
472
|
+
Loaded on every turn from `DATA_DIR` (default: `~/.tiger/data/`):
|
|
221
473
|
|
|
222
474
|
| File | Purpose |
|
|
223
475
|
|------|---------|
|
|
@@ -226,6 +478,8 @@ Loaded on every turn from `~/.tiger/data/`:
|
|
|
226
478
|
| `human2.md` | Running update log written after every conversation turn |
|
|
227
479
|
| `ownskill.md` | Known skills, workflows, and lessons learned |
|
|
228
480
|
|
|
481
|
+
> **v0.2.5 compatibility note:** If root-level legacy files already exist (for example `./soul.md`, `./ownskill.md`), Tiger mirrors updates to those files automatically. The canonical source remains `DATA_DIR`.
|
|
482
|
+
|
|
229
483
|
### Auto-Refresh Cycles
|
|
230
484
|
|
|
231
485
|
Tiger periodically regenerates these files using the LLM. All durations are configurable in `.env` (minimum 1 hour).
|
|
@@ -339,6 +593,8 @@ rm .env.secrets
|
|
|
339
593
|
| `403` quota error | Daily quota exhausted — auto-switches; raise `*_TOKEN_LIMIT` |
|
|
340
594
|
| `429` rate limit | Auto-switches to next provider in `PROVIDER_ORDER` |
|
|
341
595
|
| Z.ai auth fails | Key must be `id.secret` format (from Zhipu/BigModel console) |
|
|
596
|
+
| Telegram bot runs but does not respond | Ensure only one polling instance is running for the same bot token (stop old/global service copies) |
|
|
597
|
+
| `soul.md` / `ownskill.md` look stale | Check `DATA_DIR` first (default `~/.tiger/data`). In v0.2.5+, existing root legacy copies are mirrored automatically |
|
|
342
598
|
| Shell tool disabled | Set `ALLOW_SHELL=true` in `~/.tiger/.env` |
|
|
343
599
|
| Stuck processes | `pkill -f tiger-agent` then restart |
|
|
344
600
|
| Reset token counters | Delete `~/.tiger/db/token_usage.json` and restart |
|
package/package.json
CHANGED
package/src/agent/skills.js
CHANGED
|
@@ -135,7 +135,7 @@ async function clawhubInstall(args = {}) {
|
|
|
135
135
|
if (force) argv.push('--force');
|
|
136
136
|
|
|
137
137
|
const res = await runClawhub(argv, {
|
|
138
|
-
timeout: Number(args.timeout_ms ||
|
|
138
|
+
timeout: Number(args.timeout_ms || process.env.SWARM_AGENT_TIMEOUT_MS || 720000),
|
|
139
139
|
maxBuffer: 1024 * 1024
|
|
140
140
|
});
|
|
141
141
|
if (res.ok) {
|
package/src/apiProviders.js
CHANGED
|
@@ -134,7 +134,7 @@ function buildProviders(env) {
|
|
|
134
134
|
embedPath: '/embeddings',
|
|
135
135
|
formatRequest: standardFormat,
|
|
136
136
|
parseResponse: standardParse,
|
|
137
|
-
timeout: Number(env.KIMI_TIMEOUT_MS ||
|
|
137
|
+
timeout: Number(env.KIMI_TIMEOUT_MS || 180000)
|
|
138
138
|
},
|
|
139
139
|
|
|
140
140
|
moonshot: {
|
|
@@ -150,7 +150,7 @@ function buildProviders(env) {
|
|
|
150
150
|
embedPath: '/embeddings',
|
|
151
151
|
formatRequest: standardFormat,
|
|
152
152
|
parseResponse: standardParse,
|
|
153
|
-
timeout: Number(env.KIMI_TIMEOUT_MS ||
|
|
153
|
+
timeout: Number(env.KIMI_TIMEOUT_MS || 180000)
|
|
154
154
|
},
|
|
155
155
|
|
|
156
156
|
zai: {
|
|
@@ -171,7 +171,7 @@ function buildProviders(env) {
|
|
|
171
171
|
embedPath: '/embeddings',
|
|
172
172
|
formatRequest: standardFormat,
|
|
173
173
|
parseResponse: standardParse,
|
|
174
|
-
timeout: Number(env.ZAI_TIMEOUT_MS ||
|
|
174
|
+
timeout: Number(env.ZAI_TIMEOUT_MS || 180000)
|
|
175
175
|
},
|
|
176
176
|
|
|
177
177
|
minimax: {
|
|
@@ -187,7 +187,7 @@ function buildProviders(env) {
|
|
|
187
187
|
embedPath: '/embeddings',
|
|
188
188
|
formatRequest: standardFormat,
|
|
189
189
|
parseResponse: standardParse,
|
|
190
|
-
timeout: Number(env.MINIMAX_TIMEOUT_MS ||
|
|
190
|
+
timeout: Number(env.MINIMAX_TIMEOUT_MS || 180000)
|
|
191
191
|
},
|
|
192
192
|
|
|
193
193
|
claude: {
|
|
@@ -203,16 +203,15 @@ function buildProviders(env) {
|
|
|
203
203
|
embedPath: null, // Claude does not expose an embeddings endpoint
|
|
204
204
|
formatRequest: claudeFormat,
|
|
205
205
|
parseResponse: claudeParse,
|
|
206
|
-
timeout: Number(env.CLAUDE_TIMEOUT_MS ||
|
|
206
|
+
timeout: Number(env.CLAUDE_TIMEOUT_MS || 180000)
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
//
|
|
212
|
-
|
|
211
|
+
// Always rebuild from current process.env so runtime .env changes take effect
|
|
212
|
+
// (fixes stale timeout after PM2 restart or .env update)
|
|
213
213
|
function getProviders() {
|
|
214
|
-
|
|
215
|
-
return _providers;
|
|
214
|
+
return buildProviders(process.env);
|
|
216
215
|
}
|
|
217
216
|
|
|
218
217
|
function getProvider(id) {
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// ─── Zhipu AI (BigModel) JWT auth ──────────────────────────────────────────
|
|
6
|
+
// Their v4 API requires HS256 JWT derived from the api-key (format: "id.secret")
|
|
7
|
+
function b64url(buf) {
|
|
8
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function zhipuJwt(apiKey) {
|
|
12
|
+
const dot = apiKey.indexOf('.');
|
|
13
|
+
if (dot === -1) return apiKey; // fallback: treat as plain token
|
|
14
|
+
const id = apiKey.slice(0, dot);
|
|
15
|
+
const secret = apiKey.slice(dot + 1);
|
|
16
|
+
const now = Math.floor(Date.now() / 1000);
|
|
17
|
+
const hdr = b64url(Buffer.from(JSON.stringify({ alg: 'HS256', sign_type: 'SIGN' })));
|
|
18
|
+
const pay = b64url(Buffer.from(JSON.stringify({ api_key: id, exp: now + 3600, timestamp: now })));
|
|
19
|
+
const sig = b64url(crypto.createHmac('sha256', secret).update(`${hdr}.${pay}`).digest());
|
|
20
|
+
return `${hdr}.${pay}.${sig}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ─── OpenAI-compatible adapters ────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function standardFormat(messages, options) {
|
|
26
|
+
const payload = {
|
|
27
|
+
model: options.model,
|
|
28
|
+
messages,
|
|
29
|
+
temperature: options.temperature ?? 0.3
|
|
30
|
+
};
|
|
31
|
+
if (options.tools && options.tools.length) payload.tools = options.tools;
|
|
32
|
+
if (options.tool_choice) payload.tool_choice = options.tool_choice;
|
|
33
|
+
return payload;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function standardParse(data) {
|
|
37
|
+
const message = data.choices?.[0]?.message || {};
|
|
38
|
+
const u = data.usage || {};
|
|
39
|
+
const tokens = (u.prompt_tokens || 0) + (u.completion_tokens || 0);
|
|
40
|
+
return { message, tokens };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Claude (Anthropic) adapters ───────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
function claudeFormat(messages, options) {
|
|
46
|
+
const systemMsg = messages.find((m) => m.role === 'system');
|
|
47
|
+
const rest = messages.filter((m) => m.role !== 'system');
|
|
48
|
+
|
|
49
|
+
// Convert tool definitions: OpenAI → Claude
|
|
50
|
+
let tools;
|
|
51
|
+
if (options.tools && options.tools.length) {
|
|
52
|
+
tools = options.tools.map((t) => ({
|
|
53
|
+
name: t.function.name,
|
|
54
|
+
description: t.function.description || '',
|
|
55
|
+
input_schema: t.function.parameters || { type: 'object', properties: {} }
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Convert message content: tool_calls & tool results
|
|
60
|
+
const converted = rest.map((m) => {
|
|
61
|
+
if (m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
62
|
+
const content = [];
|
|
63
|
+
if (m.content) content.push({ type: 'text', text: m.content });
|
|
64
|
+
for (const tc of m.tool_calls) {
|
|
65
|
+
let input = {};
|
|
66
|
+
try { input = JSON.parse(tc.function.arguments || '{}'); } catch (_) {}
|
|
67
|
+
content.push({ type: 'tool_use', id: tc.id, name: tc.function.name, input });
|
|
68
|
+
}
|
|
69
|
+
return { role: 'assistant', content };
|
|
70
|
+
}
|
|
71
|
+
if (m.role === 'tool') {
|
|
72
|
+
return {
|
|
73
|
+
role: 'user',
|
|
74
|
+
content: [{ type: 'tool_result', tool_use_id: m.tool_call_id, content: String(m.content || '') }]
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return m;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const payload = {
|
|
81
|
+
model: options.model,
|
|
82
|
+
max_tokens: options.max_tokens || 8192,
|
|
83
|
+
messages: converted
|
|
84
|
+
};
|
|
85
|
+
if (systemMsg) payload.system = systemMsg.content;
|
|
86
|
+
if (tools && tools.length) {
|
|
87
|
+
payload.tools = tools;
|
|
88
|
+
// Convert OpenAI tool_choice format → Claude format
|
|
89
|
+
const tc = options.tool_choice;
|
|
90
|
+
if (tc && tc !== 'none') {
|
|
91
|
+
if (tc === 'auto' || tc === 'required') {
|
|
92
|
+
payload.tool_choice = { type: tc === 'required' ? 'any' : 'auto' };
|
|
93
|
+
} else if (tc && typeof tc === 'object' && tc.type === 'function') {
|
|
94
|
+
payload.tool_choice = { type: 'tool', name: tc.function.name };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return payload;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function claudeParse(data) {
|
|
102
|
+
const content = Array.isArray(data.content) ? data.content : [];
|
|
103
|
+
const textBlock = content.find((b) => b.type === 'text');
|
|
104
|
+
const toolUseBlocks = content.filter((b) => b.type === 'tool_use');
|
|
105
|
+
|
|
106
|
+
const message = { role: 'assistant', content: textBlock ? textBlock.text : '' };
|
|
107
|
+
if (toolUseBlocks.length) {
|
|
108
|
+
message.tool_calls = toolUseBlocks.map((tb) => ({
|
|
109
|
+
id: tb.id,
|
|
110
|
+
type: 'function',
|
|
111
|
+
function: { name: tb.name, arguments: JSON.stringify(tb.input || {}) }
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const u = data.usage || {};
|
|
116
|
+
const tokens = (u.input_tokens || 0) + (u.output_tokens || 0);
|
|
117
|
+
return { message, tokens };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Provider registry ─────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
function buildProviders(env) {
|
|
123
|
+
return {
|
|
124
|
+
kimi: {
|
|
125
|
+
id: 'kimi',
|
|
126
|
+
name: 'Kimi Code',
|
|
127
|
+
baseUrl: (env.KIMI_BASE_URL || 'https://api.kimi.com/coding/v1').replace(/\/$/, ''),
|
|
128
|
+
chatModel: env.KIMI_CHAT_MODEL ? env.KIMI_CHAT_MODEL.replace(/^kimi-coding\//, '') : 'k2p5',
|
|
129
|
+
embedModel: env.KIMI_EMBED_MODEL || '',
|
|
130
|
+
apiKey: env.KIMI_CODE_API_KEY || env.KIMI_API_KEY || '',
|
|
131
|
+
userAgent: env.KIMI_USER_AGENT || 'KimiCLI/0.77',
|
|
132
|
+
authHeaders: (key) => ({ Authorization: `Bearer ${key}` }),
|
|
133
|
+
chatPath: '/chat/completions',
|
|
134
|
+
embedPath: '/embeddings',
|
|
135
|
+
formatRequest: standardFormat,
|
|
136
|
+
parseResponse: standardParse,
|
|
137
|
+
timeout: Number(env.KIMI_TIMEOUT_MS || 30000)
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
moonshot: {
|
|
141
|
+
id: 'moonshot',
|
|
142
|
+
name: 'Kimi Moonshot',
|
|
143
|
+
baseUrl: (env.MOONSHOT_BASE_URL || 'https://api.moonshot.cn/v1').replace(/\/$/, ''),
|
|
144
|
+
chatModel: env.MOONSHOT_MODEL || 'kimi-k1',
|
|
145
|
+
embedModel: env.MOONSHOT_EMBED_MODEL || 'kimi-embedding-v1',
|
|
146
|
+
apiKey: env.MOONSHOT_API_KEY || env.KIMI_API_KEY || '',
|
|
147
|
+
userAgent: '',
|
|
148
|
+
authHeaders: (key) => ({ Authorization: `Bearer ${key}` }),
|
|
149
|
+
chatPath: '/chat/completions',
|
|
150
|
+
embedPath: '/embeddings',
|
|
151
|
+
formatRequest: standardFormat,
|
|
152
|
+
parseResponse: standardParse,
|
|
153
|
+
timeout: Number(env.KIMI_TIMEOUT_MS || 30000)
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
zai: {
|
|
157
|
+
id: 'zai',
|
|
158
|
+
name: 'Z.ai',
|
|
159
|
+
baseUrl: (env.ZAI_BASE_URL || 'https://api.z.ai/api/coding/paas/v4').replace(/\/$/, ''),
|
|
160
|
+
chatModel: env.ZAI_MODEL || 'glm-4.7',
|
|
161
|
+
embedModel: env.ZAI_EMBED_MODEL || '',
|
|
162
|
+
apiKey: env.ZAI_API_KEY || '',
|
|
163
|
+
userAgent: '',
|
|
164
|
+
// api.z.ai uses plain Bearer; old bigmodel.cn used Zhipu JWT
|
|
165
|
+
authHeaders: (key) => {
|
|
166
|
+
const baseUrl = (env.ZAI_BASE_URL || '').toLowerCase();
|
|
167
|
+
if (baseUrl.includes('bigmodel.cn')) return { Authorization: `Bearer ${zhipuJwt(key)}` };
|
|
168
|
+
return { Authorization: `Bearer ${key}` };
|
|
169
|
+
},
|
|
170
|
+
chatPath: '/chat/completions',
|
|
171
|
+
embedPath: '/embeddings',
|
|
172
|
+
formatRequest: standardFormat,
|
|
173
|
+
parseResponse: standardParse,
|
|
174
|
+
timeout: Number(env.ZAI_TIMEOUT_MS || 30000)
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
minimax: {
|
|
178
|
+
id: 'minimax',
|
|
179
|
+
name: 'MiniMax',
|
|
180
|
+
baseUrl: (env.MINIMAX_BASE_URL || 'https://api.minimax.chat/v1').replace(/\/$/, ''),
|
|
181
|
+
chatModel: env.MINIMAX_MODEL || 'abab6.5s-chat',
|
|
182
|
+
embedModel: env.MINIMAX_EMBED_MODEL || '',
|
|
183
|
+
apiKey: env.MINIMAX_API_KEY || '',
|
|
184
|
+
userAgent: '',
|
|
185
|
+
authHeaders: (key) => ({ Authorization: `Bearer ${key}` }),
|
|
186
|
+
chatPath: '/chat/completions',
|
|
187
|
+
embedPath: '/embeddings',
|
|
188
|
+
formatRequest: standardFormat,
|
|
189
|
+
parseResponse: standardParse,
|
|
190
|
+
timeout: Number(env.MINIMAX_TIMEOUT_MS || 30000)
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
claude: {
|
|
194
|
+
id: 'claude',
|
|
195
|
+
name: 'Claude (Anthropic)',
|
|
196
|
+
baseUrl: (env.CLAUDE_BASE_URL || 'https://api.anthropic.com').replace(/\/$/, ''),
|
|
197
|
+
chatModel: env.CLAUDE_MODEL || 'claude-sonnet-4-6',
|
|
198
|
+
embedModel: '',
|
|
199
|
+
apiKey: env.CLAUDE_API_KEY || env.ANTHROPIC_API_KEY || '',
|
|
200
|
+
userAgent: '',
|
|
201
|
+
authHeaders: (key) => ({ 'x-api-key': key, 'anthropic-version': '2023-06-01' }),
|
|
202
|
+
chatPath: '/v1/messages',
|
|
203
|
+
embedPath: null, // Claude does not expose an embeddings endpoint
|
|
204
|
+
formatRequest: claudeFormat,
|
|
205
|
+
parseResponse: claudeParse,
|
|
206
|
+
timeout: Number(env.CLAUDE_TIMEOUT_MS || 60000)
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Singleton — providers are built once from process.env on first access
|
|
212
|
+
let _providers = null;
|
|
213
|
+
function getProviders() {
|
|
214
|
+
if (!_providers) _providers = buildProviders(process.env);
|
|
215
|
+
return _providers;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getProvider(id) {
|
|
219
|
+
return getProviders()[id] || null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = { getProviders, getProvider };
|
package/src/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ const { startReflectionScheduler } = require('./agent/reflectionScheduler');
|
|
|
9
9
|
const { initVectorMemory } = require('./agent/db');
|
|
10
10
|
const { startTelegramBot } = require('./telegram/bot');
|
|
11
11
|
const { handleMessage } = require('./agent/mainAgent');
|
|
12
|
+
const { ensureSwarmLayout } = require('./swarm');
|
|
12
13
|
|
|
13
14
|
// Source root — always inside the npm package
|
|
14
15
|
const srcRoot = path.resolve(__dirname, '..');
|
|
@@ -51,6 +52,7 @@ function getExistingSupervisorPid() {
|
|
|
51
52
|
|
|
52
53
|
function startTelegramInBackground() {
|
|
53
54
|
ensureContextFiles();
|
|
55
|
+
ensureSwarmLayout();
|
|
54
56
|
const existingPid = getExistingSupervisorPid();
|
|
55
57
|
if (existingPid && isPidRunning(existingPid)) {
|
|
56
58
|
process.stdout.write(`Telegram background bot is already running (PID ${existingPid}).\n`);
|
|
@@ -113,6 +115,7 @@ function printVectorMemoryStatus(vectorStatus) {
|
|
|
113
115
|
|
|
114
116
|
async function runCli() {
|
|
115
117
|
ensureContextFiles();
|
|
118
|
+
ensureSwarmLayout();
|
|
116
119
|
startReflectionScheduler();
|
|
117
120
|
const vectorStatus = initVectorMemory();
|
|
118
121
|
printVectorMemoryStatus(vectorStatus);
|
|
@@ -171,6 +174,7 @@ async function main() {
|
|
|
171
174
|
|
|
172
175
|
if (isTelegramMode(argv)) {
|
|
173
176
|
ensureContextFiles();
|
|
177
|
+
ensureSwarmLayout();
|
|
174
178
|
startReflectionScheduler();
|
|
175
179
|
const vectorStatus = initVectorMemory();
|
|
176
180
|
printVectorMemoryStatus(vectorStatus);
|