tiger-agent 0.2.4 → 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 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
@@ -1,16 +1,32 @@
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
  [![npm version](https://img.shields.io/npm/v/tiger-agent.svg)](https://www.npmjs.com/package/tiger-agent)
4
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
9
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
6
10
 
7
- **Cognitive AI Agent** with persistent long-term memory, multi-provider LLM support, self-learning, and Telegram bot integration — designed for 24/7 autonomous operation on Linux.
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.
8
12
 
9
13
  Made by **AI Research Group, Department of Civil Engineering, KMUTT**
10
14
 
11
15
  ---
12
16
 
13
- ## 🆕 What's New — v0.2.4
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
14
30
 
15
31
  - **ClawHub skill install fixed** — `clawhub_install` and `clawhub_search` now work correctly when installed via `npm install -g`
16
32
  - **No required API keys** — `tiger onboard` skips providers with no key; any single provider is enough to start
@@ -96,6 +112,13 @@ tiger status # check if running
96
112
  tiger stop # stop
97
113
  ```
98
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
+
99
122
  Logs: `~/.tiger/logs/telegram.out.log`
100
123
 
101
124
  ---
@@ -141,7 +164,7 @@ Logs: `~/.tiger/logs/telegram.out.log`
141
164
  | `ALLOW_SHELL` | `false` | Enable shell tool |
142
165
  | `ALLOW_SKILL_INSTALL` | `false` | Enable ClawHub skill install |
143
166
  | `VECTOR_DB_PATH` | `~/.tiger/db/memory.sqlite` | SQLite vector DB path |
144
- | `DATA_DIR` | `~/.tiger/data` | Context files directory |
167
+ | `DATA_DIR` | `~/.tiger/data` | Canonical context files directory |
145
168
  | `OWN_SKILL_UPDATE_HOURS` | `24` | Hours between `ownskill.md` regenerations (min 1) |
146
169
  | `SOUL_UPDATE_HOURS` | `24` | Hours between `soul.md` regenerations (min 1) |
147
170
  | `REFLECTION_UPDATE_HOURS` | `12` | Hours between reflection cycles (min 1) |
@@ -184,6 +207,22 @@ ZAI_TOKEN_LIMIT=100000
184
207
  MINIMAX_TOKEN_LIMIT=100000
185
208
  CLAUDE_TOKEN_LIMIT=500000
186
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
187
226
  ```
188
227
 
189
228
  ### Auto-Switch Behaviour
@@ -205,15 +244,232 @@ MOONSHOT_TOKEN_LIMIT=100000
205
244
  | `/tokens` | Show today's token usage per provider |
206
245
  | `/limit` | Show daily token limits per provider |
207
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 |
208
264
  | `/help` | Show all commands |
209
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
+
210
466
  ---
211
467
 
212
468
  ## 🧠 Memory & Context
213
469
 
214
470
  ### Context Files
215
471
 
216
- Loaded on every turn from `~/.tiger/data/`:
472
+ Loaded on every turn from `DATA_DIR` (default: `~/.tiger/data/`):
217
473
 
218
474
  | File | Purpose |
219
475
  |------|---------|
@@ -222,6 +478,8 @@ Loaded on every turn from `~/.tiger/data/`:
222
478
  | `human2.md` | Running update log written after every conversation turn |
223
479
  | `ownskill.md` | Known skills, workflows, and lessons learned |
224
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
+
225
483
  ### Auto-Refresh Cycles
226
484
 
227
485
  Tiger periodically regenerates these files using the LLM. All durations are configurable in `.env` (minimum 1 hour).
@@ -335,6 +593,8 @@ rm .env.secrets
335
593
  | `403` quota error | Daily quota exhausted — auto-switches; raise `*_TOKEN_LIMIT` |
336
594
  | `429` rate limit | Auto-switches to next provider in `PROVIDER_ORDER` |
337
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 |
338
598
  | Shell tool disabled | Set `ALLOW_SHELL=true` in `~/.tiger/.env` |
339
599
  | Stuck processes | `pkill -f tiger-agent` then restart |
340
600
  | Reset token counters | Delete `~/.tiger/db/token_usage.json` and restart |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiger-agent",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "description": "Cognitive AI agent with persistent memory, multi-provider LLM, and Telegram bot",
5
5
  "type": "commonjs",
6
6
  "main": "src/cli.js",
@@ -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
- fs.writeFileSync(full, `# ${name.replace('.md', '')}\n\n`, 'utf8');
14
+ writeContextFile(full, `# ${name.replace('.md', '')}\n\n`);
15
+ continue;
14
16
  }
17
+ syncLegacyRootMirror(full);
15
18
  }
16
19
  }
17
20
 
@@ -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
- fs.appendFileSync(path.resolve(human2.full), block, 'utf8');
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
- fs.writeFileSync(path.resolve(ownSkillPath), `${next}\n`, 'utf8');
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
- fs.writeFileSync(path.resolve(soulPath), `${next}\n`, 'utf8');
213
+ writeContextFile(path.resolve(soulPath), `${next}\n`);
213
214
  setMeta(SOUL_META_KEY, Date.now());
214
215
  }
215
216
 
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const { chatCompletion, embedText } = require('../llmClient');
4
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
- fs.writeFileSync(full, next, 'utf8');
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
- fs.writeFileSync(full, `${existing.trimEnd()}${block}`, 'utf8');
84
+ writeContextFile(full, `${existing.trimEnd()}${block}`);
84
85
  }
85
86
 
86
87
  async function generateReflection(rows, sinceIso, untilIso) {
@@ -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 || 120000),
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) {
@@ -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 || 30000)
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 || 30000)
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 || 30000)
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 || 30000)
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 || 60000)
206
+ timeout: Number(env.CLAUDE_TIMEOUT_MS || 180000)
207
207
  }
208
208
  };
209
209
  }
210
210
 
211
- // Singleton providers are built once from process.env on first access
212
- let _providers = null;
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
- if (!_providers) _providers = buildProviders(process.env);
215
- return _providers;
214
+ return buildProviders(process.env);
216
215
  }
217
216
 
218
217
  function getProvider(id) {