tg-agent 1.1.3 → 1.1.8

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
@@ -6,7 +6,7 @@
6
6
 
7
7
  - Telegram DM 交互(仅私聊)
8
8
  - 多会话管理:/new /list /use /close /reset
9
- - 会话持久化:默认保存到 `~/.codex/tg-sessions`
9
+ - 会话持久化:默认保存到 `~/.tg-agent/tg-sessions`
10
10
  - 运行中可取消:/stop
11
11
  - 自动 Markdown 渲染:优先 MarkdownV2,失败回退 Markdown,再回退纯文本
12
12
  - 多 Provider / 多 Model 选择(支持 Codex / antigravity / Gemini CLI 等)
@@ -23,22 +23,13 @@
23
23
  npm install
24
24
  ```
25
25
 
26
- 2) 配置环境变量
26
+ 2) 配置文件(可选)
27
27
 
28
- 复制并修改 `.env`:
28
+ 你可以手动创建 `~/.tg-agent/config.toml`,最小示例如下:
29
29
 
30
- ```bash
31
- cp .env.example .env
32
- ```
33
-
34
- 最小必需配置示例:
35
-
36
- ```env
37
- TELEGRAM_BOT_TOKEN=YOUR_BOT_TOKEN
38
- TELEGRAM_ALLOWED_USER_IDS=123456789
39
- PROXY_URL=socks5h://127.0.0.1:7890
40
- LLM_PROVIDER=openai-codex
41
- LLM_MODEL=codex-latest
30
+ ```toml
31
+ [telegram]
32
+ bot_token = "YOUR_BOT_TOKEN"
42
33
  ```
43
34
 
44
35
  3) 运行
@@ -47,6 +38,8 @@ LLM_MODEL=codex-latest
47
38
  npm run dev
48
39
  ```
49
40
 
41
+ 首次运行会提示输入 `telegram.bot_token` 并写入配置文件。
42
+
50
43
  或构建后运行:
51
44
 
52
45
  ```bash
@@ -81,25 +74,38 @@ npm start
81
74
  - 图片会作为多模态输入传给模型(若模型支持图像)。
82
75
  - Agent 可使用 `send_photo` / `send_file` 工具把本地文件发回 Telegram(路径需在工作目录或 uploads 下)。
83
76
 
84
- ## 环境变量
77
+ ## 配置文件
78
+
79
+ 所有配置统一放在 `~/.tg-agent/config.toml`。首次启动会提示输入 `telegram.bot_token`,其余字段使用默认值。
85
80
 
86
- `.env.example` 已列出所有可配置项。常用说明如下:
81
+ 常用字段示例:
87
82
 
88
- - `TELEGRAM_BOT_TOKEN`:必填
89
- - `TELEGRAM_ALLOWED_USER_IDS`:可选,逗号分隔白名单用户 ID
90
- - `AGENT_DIR`:agent 配置与缓存目录(默认 `~/.tg-agent`)
91
- - `WORKSPACE_DIR`:工具操作的工作目录
92
- - `SESSION_DIR`:会话持久化目录
93
- - `LLM_PROVIDER`:模型提供方(默认 `openai-codex`)
94
- - `LLM_MODEL`:模型 ID,例如 `codex-latest`
95
- - `PROXY_URL`:模型与全局流量代理
96
- - `FETCH_PROXY_URL`:仅用于 `fetch` 工具的代理(必须是 HTTP/HTTPS)
97
- MCP 配置通过 `~/.tg-agent/config.toml` 提供,示例见下。
83
+ ```toml
84
+ [telegram]
85
+ bot_token = "YOUR_BOT_TOKEN"
86
+ allowed_user_ids = ["123456789"]
87
+ parse_mode = ""
88
+
89
+ [model]
90
+ provider = "openai-codex"
91
+ model = "gpt-5.2"
92
+ openai_api_key = ""
93
+
94
+ [paths]
95
+ workspace_dir = ""
96
+ session_dir = "~/.tg-agent/tg-sessions"
97
+
98
+ [proxy]
99
+ url = ""
100
+
101
+ [fetch]
102
+ proxy_url = ""
103
+ ```
98
104
 
99
105
  ## Codex 认证
100
106
 
101
107
  优先使用本机 Codex OAuth 凭据(`codex login` 写入的配置)。
102
- 也可以在 Telegram 内执行 `/login openai-codex`,将凭据保存到 `AGENT_DIR/auth.json`。
108
+ 也可以在 Telegram 内执行 `/login openai-codex`,将凭据保存到 `~/.tg-agent/auth.json`。
103
109
  如果运行时提示缺少权限或无法找到凭据,请重新执行:
104
110
 
105
111
  ```bash
@@ -108,7 +114,7 @@ codex login
108
114
 
109
115
  ## MCP 配置
110
116
 
111
- `~/.tg-agent/config.toml` 中添加 MCP 服务器:
117
+ 在同一个 `~/.tg-agent/config.toml` 中添加 MCP 服务器:
112
118
 
113
119
  ```toml
114
120
  [mcp_servers.context7]
@@ -155,5 +161,5 @@ tg-agent
155
161
 
156
162
  ## 安全说明
157
163
 
158
- - 不要提交 `.env` 到仓库。
159
- - 建议开启 `TELEGRAM_ALLOWED_USER_IDS` 白名单。
164
+ - 不要提交 `~/.tg-agent/config.toml` 到仓库。
165
+ - 建议开启 `telegram.allowed_user_ids` 白名单。
package/dist/auth.js CHANGED
@@ -1 +1 @@
1
- import{config as s}from"./config.js";import{readCodexOAuth as c}from"./codexAuth.js";function l(e){const r=e.trim().toLowerCase();if(r==="openai-codex"||r==="codex"){const o=c();if(o)return{apiKey:o.accessToken,source:`codex:${o.source}`};throw new Error("No Codex OAuth credentials found. Run `codex login`.")}if(s.openaiApiKey)return{apiKey:s.openaiApiKey,source:"env:OPENAI_API_KEY"};throw new Error(`No API key for provider: ${e}`)}function u(){const e=[{key:"PROXY_URL",value:process.env.PROXY_URL},{key:"FETCH_PROXY_URL",value:process.env.FETCH_PROXY_URL},{key:"HTTPS_PROXY",value:process.env.HTTPS_PROXY},{key:"https_proxy",value:process.env.https_proxy},{key:"HTTP_PROXY",value:process.env.HTTP_PROXY},{key:"http_proxy",value:process.env.http_proxy},{key:"ALL_PROXY",value:process.env.ALL_PROXY},{key:"all_proxy",value:process.env.all_proxy}];for(const r of e){const o=r.value?.trim();if(o)return{url:o,source:r.key}}return null}function n(e){const r=e.toLowerCase();return r.startsWith("socks5://")||r.startsWith("socks4://")||r.startsWith("socks://")?"socks":r.startsWith("https://")?"https":"http"}function y(){const e=u();return e?{url:e.url,kind:n(e.url),source:e.source}:null}function a(){const e=[{key:"FETCH_PROXY_URL",value:process.env.FETCH_PROXY_URL},{key:"HTTPS_PROXY",value:process.env.HTTPS_PROXY},{key:"https_proxy",value:process.env.https_proxy},{key:"HTTP_PROXY",value:process.env.HTTP_PROXY},{key:"http_proxy",value:process.env.http_proxy}];for(const r of e){const o=r.value?.trim();if(!o)continue;const t=n(o);if(t!=="socks")return{url:o,kind:t,source:r.key}}return null}export{l as resolveApiKeyForProvider,a as resolveFetchProxyInfo,y as resolveProxyInfo};
1
+ import{config as e}from"./config.js";import{readCodexOAuth as i}from"./codexAuth.js";function f(r){const o=r.trim().toLowerCase();if(o==="openai-codex"||o==="codex"){const t=i();if(t)return{apiKey:t.accessToken,source:`codex:${t.source}`};throw new Error("No Codex OAuth credentials found. Run `codex login`.")}if(e.openaiApiKey)return{apiKey:e.openaiApiKey,source:"config.model.openai_api_key"};throw new Error(`No API key for provider: ${r}`)}function s(){return e.proxyUrl?{url:e.proxyUrl,source:"config.proxy.url"}:e.fetchProxyUrl?{url:e.fetchProxyUrl,source:"config.fetch.proxy_url"}:null}function c(r){const o=r.toLowerCase();return o.startsWith("socks5://")||o.startsWith("socks4://")||o.startsWith("socks://")?"socks":o.startsWith("https://")?"https":"http"}function p(){const r=s();return r?{url:r.url,kind:c(r.url),source:r.source}:null}function y(){const r=[{key:"config.fetch.proxy_url",value:e.fetchProxyUrl},{key:"config.proxy.url",value:e.proxyUrl}];for(const o of r){const t=o.value?.trim();if(!t)continue;const n=c(t);if(n!=="socks")return{url:t,kind:n,source:o.key}}return null}export{f as resolveApiKeyForProvider,y as resolveFetchProxyInfo,p as resolveProxyInfo};
package/dist/cli.js CHANGED
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import"./index.js";
2
+ import u from"node:fs";import i from"node:path";import g from"node:readline/promises";import{fileURLToPath as _}from"node:url";import{applyConfigDefaults as d,getDefaultConfigFile as m,loadConfigFile as a,readTelegramToken as T,refreshConfig as v,saveConfigFile as c,setTelegramToken as E}from"./config.js";const p=process.argv.slice(2),l=(e,t)=>p.includes(e)||(t?p.includes(t):!1),h=l("--help","-h"),y=l("--version","-v"),w=["Usage: tg-agent [options]","","Options:"," -h, --help Show help"," -v, --version Show version"].join(`
3
+ `),R=()=>{if(process.env.npm_package_version)return process.env.npm_package_version;try{const e=_(import.meta.url),t=i.dirname(e),s=i.join(t,"..","package.json"),n=u.readFileSync(s,"utf8"),o=JSON.parse(n);if(o.version)return o.version}catch{}return"dev"};h&&(console.log(w),process.exit(0)),y&&(console.log(R()),process.exit(0));async function C(){const e=g.createInterface({input:process.stdin,output:process.stdout});try{return(await e.question("Enter TELEGRAM_BOT_TOKEN: ")).trim()}finally{e.close()}}async function O(){const{configPath:e,data:t,exists:s}=a(),n=m(),o=s?t:n,f=d(o,n);if(T(o)){f&&await c(e,o);return}process.stdin.isTTY||(console.error(`Missing telegram.bot_token in ${e}.`),process.exit(1));const r=await C();r||(console.error("Empty TELEGRAM_BOT_TOKEN."),process.exit(1)),E(o,r),await c(e,o),console.log(`Saved config to ${e}.`)}async function k(){const{data:e}=a(),t=typeof e.tls=="object"&&e.tls?e.tls:{},s=typeof t.extra_ca_certs=="string"?t.extra_ca_certs.trim():"";if(s&&(process.env.NODE_EXTRA_CA_CERTS=s),typeof t.reject_unauthorized=="boolean"){process.env.NODE_TLS_REJECT_UNAUTHORIZED=t.reject_unauthorized?"1":"0";return}const n=typeof t.reject_unauthorized=="string"?t.reject_unauthorized.trim():"";n&&(process.env.NODE_TLS_REJECT_UNAUTHORIZED=n)}await O(),await k(),v(),import("./index.js");
package/dist/codexAuth.js CHANGED
@@ -1 +1 @@
1
- import{createHash as l}from"node:crypto";import u from"node:fs";import m from"node:os";import f from"node:path";import{execSync as d}from"node:child_process";const y="auth.json",x="Codex Auth";function p(){const e=process.env.CODEX_HOME?.trim();return C(e||"~/.codex")}function k(){return f.join(p(),y)}function C(e){return e.startsWith("~")?f.join(m.homedir(),e.slice(1)):e}function g(e){return`cli|${l("sha256").update(e).digest("hex").slice(0,16)}`}function _(){if(process.platform!=="darwin")return null;const e=p(),o=g(e);try{const i=d(`security find-generic-password -s "${x}" -a "${o}" -w`,{encoding:"utf8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim(),s=JSON.parse(i),n=s.tokens,t=n?.access_token,r=n?.refresh_token;if(typeof t!="string"||!t||typeof r!="string"||!r)return null;const c=s.last_refresh,a=typeof c=="string"||typeof c=="number"?new Date(c).getTime():Date.now(),h=Number.isFinite(a)?a+3600*1e3:Date.now()+3600*1e3;return{accessToken:t,refreshToken:r,expiresAt:h,source:"keychain"}}catch{return null}}function w(){const e=k();try{const o=u.readFileSync(e,"utf8"),s=JSON.parse(o).tokens,n=s?.access_token,t=s?.refresh_token;if(typeof n!="string"||!n||typeof t!="string"||!t)return null;let r=Date.now()+3600*1e3;try{r=u.statSync(e).mtimeMs+3600*1e3}catch{}return{accessToken:n,refreshToken:t,expiresAt:r,source:"file"}}catch{return null}}function F(){return _()??w()}export{F as readCodexOAuth};
1
+ import{createHash as h}from"node:crypto";import u from"node:fs";import l from"node:path";import{execSync as m}from"node:child_process";import{config as f}from"./config.js";const d="auth.json",y="Codex Auth";function k(){return l.join(f.codexHome,d)}function x(t){return`cli|${h("sha256").update(t).digest("hex").slice(0,16)}`}function g(){if(process.platform!=="darwin")return null;const t=f.codexHome,s=x(t);try{const i=m(`security find-generic-password -s "${y}" -a "${s}" -w`,{encoding:"utf8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim(),r=JSON.parse(i),n=r.tokens,e=n?.access_token,o=n?.refresh_token;if(typeof e!="string"||!e||typeof o!="string"||!o)return null;const c=r.last_refresh,a=typeof c=="string"||typeof c=="number"?new Date(c).getTime():Date.now(),p=Number.isFinite(a)?a+3600*1e3:Date.now()+3600*1e3;return{accessToken:e,refreshToken:o,expiresAt:p,source:"keychain"}}catch{return null}}function w(){const t=k();try{const s=u.readFileSync(t,"utf8"),r=JSON.parse(s).tokens,n=r?.access_token,e=r?.refresh_token;if(typeof n!="string"||!n||typeof e!="string"||!e)return null;let o=Date.now()+3600*1e3;try{o=u.statSync(t).mtimeMs+3600*1e3}catch{}return{accessToken:n,refreshToken:e,expiresAt:o,source:"file"}}catch{return null}}function F(){return g()??w()}export{F as readCodexOAuth};
package/dist/config.js CHANGED
@@ -1 +1 @@
1
- import t from"node:path";import i from"dotenv";import{expandHome as n}from"./utils.js";i.config();function s(e,o){if(!e)return o;const r=Number.parseInt(e,10);return Number.isNaN(r)?o:r}function c(e){if(!e)return new Set;const o=e.split(",").map(r=>r.trim()).filter(r=>r.length>0);return new Set(o)}const p=process.env.SESSION_DIR??"~/.codex/tg-sessions",m=process.env.AGENT_DIR??"~/.tg-agent",a=process.env.WORKSPACE_DIR??process.cwd(),E=process.env.LLM_PROVIDER??"openai-codex",_=process.env.LLM_MODEL??process.env.CODEX_MODEL??"gpt-5.2",M=process.env.TELEGRAM_PARSE_MODE??"",T=process.env.TELEGRAM_ALLOWED_USER_IDS??"",S={telegramToken:process.env.TELEGRAM_BOT_TOKEN?.trim()??"",openaiApiKey:process.env.OPENAI_API_KEY?.trim()??"",modelProvider:E.trim(),modelRef:_.trim(),telegramParseMode:M.trim(),telegramAllowedUsers:c(T),sessionDir:n(p),agentDir:n(m),workspaceDir:t.resolve(n(a)),maxSessions:s(process.env.MAX_SESSIONS,3),maxConcurrent:s(process.env.MAX_CONCURRENT,3),maxHistoryMessages:s(process.env.MAX_HISTORY_MESSAGES,40),maxOutputTokens:s(process.env.MAX_OUTPUT_TOKENS,0),fetchMaxBytes:s(process.env.FETCH_MAX_BYTES,2e5),fetchTimeoutMs:s(process.env.FETCH_TIMEOUT_MS,6e4),modelTimeoutMs:s(process.env.MODEL_TIMEOUT_MS,6e4),modelTimeoutStreamingMs:s(process.env.MODEL_TIMEOUT_STREAM_MS,3e5),systemPrompt:process.env.SYSTEM_PROMPT??"You are running in user's personal computer or VPS, using telegram bot to interact with user. Be concise and practical."};function v(){const e=[];if(S.telegramToken||e.push("TELEGRAM_BOT_TOKEN"),e.length>0)throw new Error(`Missing env vars: ${e.join(", ")}`)}export{v as assertConfig,S as config};
1
+ import w from"node:fs";import f from"node:path";import*as k from"@iarna/toml";import{ensureDir as R,expandHome as c}from"./utils.js";const b="~/.tg-agent",z="config.toml",S="You are running in user's personal computer or VPS, using telegram bot to interact with user. Be concise and practical.";function T(){const t=c(b);return f.join(t,z)}function o(t,e){const r=t[e];return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}function n(t,e){return typeof t=="string"?t:typeof t=="number"||typeof t=="boolean"?String(t):e}function a(t,e){if(typeof t=="number"&&Number.isFinite(t))return t;if(typeof t=="string"){const r=Number.parseInt(t,10);return Number.isNaN(r)?e:r}return e}function l(t,e){if(typeof t=="boolean")return t;if(typeof t=="string"){const r=t.trim().toLowerCase();if(r==="true"||r==="1"||r==="yes")return!0;if(r==="false"||r==="0"||r==="no")return!1}return e}function B(t){return Array.isArray(t)?t.map(e=>typeof e=="string"?e:String(e)).map(e=>e.trim()).filter(Boolean):typeof t=="string"?t.split(",").map(e=>e.trim()).filter(Boolean):[]}function O(t){return new Set(B(t))}function U(t){const e=new Map;for(const[r,s]of Object.entries(t))typeof s=="string"&&s.trim()&&e.set(r,f.resolve(c(s.trim())));return e}function A(){const t=T();try{const e=w.readFileSync(t,"utf8"),r=k.parse(e);return{configPath:t,data:r,exists:!0}}catch{return{configPath:t,data:{},exists:!1}}}async function G(t,e){await R(f.dirname(t));const r=k.stringify(e);await w.promises.writeFile(t,r,"utf8")}function Y(){return{telegram:{bot_token:"",allowed_user_ids:[],parse_mode:""},model:{provider:"openai-codex",model:"gpt-5.2",openai_api_key:""},paths:{workspace_dir:"",session_dir:"~/.tg-agent/tg-sessions"},workspace_mappings:{},limits:{max_sessions:5,max_concurrent:5,max_history_messages:40,max_output_tokens:0},timeouts:{model_timeout_ms:6e4,model_timeout_stream_ms:3e5,fetch_timeout_ms:6e4},fetch:{max_bytes:2e5,proxy_url:""},proxy:{url:""},auth:{codex_home:"~/.codex"},logging:{agent_events:!0,agent_stream:!0},system:{prompt:S},tls:{extra_ca_certs:"",reject_unauthorized:""}}}function _(t){return!!(t&&typeof t=="object"&&!Array.isArray(t))}function $(t,e){let r=!1;const s=(i,p)=>{for(const[m,u]of Object.entries(p)){if(!(m in i)){i[m]=u,r=!0;continue}const g=i[m];_(g)&&_(u)&&s(g,u)}};return _(t)&&_(e)&&s(t,e),r}function K(t){const e=o(t,"telegram");return n(e.bot_token,"").trim()}function q(t,e){const r=o(t,"telegram");r.bot_token=e.trim(),t.telegram=r}function M(t){const e=o(t,"telegram"),r=o(t,"model"),s=o(t,"paths"),i=o(t,"limits"),p=o(t,"timeouts"),m=o(t,"fetch"),u=o(t,"proxy"),g=o(t,"auth"),y=o(t,"logging"),C=o(t,"system"),x=o(t,"tls"),j=o(t,"workspace_mappings"),D=n(s.workspace_dir,process.cwd()),E=n(s.session_dir,"~/.tg-agent/tg-sessions"),N=U(j),h=n(x.reject_unauthorized,""),P=h===""?null:l(h,!0);return{telegramToken:n(e.bot_token,"").trim(),telegramAllowedUsers:O(e.allowed_user_ids),telegramParseMode:n(e.parse_mode,"").trim(),modelProvider:n(r.provider,"openai-codex").trim(),modelRef:n(r.model,"gpt-5.2").trim(),openaiApiKey:n(r.openai_api_key,"").trim(),sessionDir:c(E),agentDir:c(b),workspaceDir:f.resolve(c(D)),workspaceMappings:N,maxSessions:a(i.max_sessions,5),maxConcurrent:a(i.max_concurrent,5),maxHistoryMessages:a(i.max_history_messages,40),maxOutputTokens:a(i.max_output_tokens,0),fetchMaxBytes:a(m.max_bytes,2e5),fetchTimeoutMs:a(p.fetch_timeout_ms,6e4),modelTimeoutMs:a(p.model_timeout_ms,6e4),modelTimeoutStreamingMs:a(p.model_timeout_stream_ms,3e5),systemPrompt:n(C.prompt,S),proxyUrl:n(u.url,"").trim(),fetchProxyUrl:n(m.proxy_url,"").trim(),codexHome:c(n(g.codex_home,"~/.codex")),logAgentEvents:l(y.agent_events,!0),logAgentStream:l(y.agent_stream,!0),tlsExtraCaCerts:n(x.extra_ca_certs,"").trim(),tlsRejectUnauthorized:P}}const{data:L}=A();let d=M(L);function J(){const{data:t}=A();return d=M(t),d}function Q(){const t=[];if(d.telegramToken||t.push("telegram.bot_token"),t.length>0)throw new Error(`Missing config values: ${t.join(", ")} (edit ${T()})`)}export{$ as applyConfigDefaults,Q as assertConfig,d as config,T as getConfigPath,Y as getDefaultConfigFile,A as loadConfigFile,K as readTelegramToken,J as refreshConfig,G as saveConfigFile,q as setTelegramToken};
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
- import N from"node:path";import W from"node:fs/promises";import me from"node-telegram-bot-api";import{discoverAuthStorage as fe,discoverModels as ge}from"@mariozechner/pi-coding-agent";import{getOAuthProviders as pe}from"@mariozechner/pi-ai";import{config as m,assertConfig as we}from"./config.js";import{resolveApiKeyForProvider as he,resolveProxyInfo as ye}from"./auth.js";import{applyFetchProxy as $e}from"./proxy.js";import{runPiAgentPrompt as ve}from"./piAgentRunner.js";import{readCodexOAuth as Me}from"./codexAuth.js";import{formatMcpTarget as Se,freezeMcpCatalog as X,getMcpCatalog as H,loadMcpServers as _e,loadMcpServersSync as Ae,probeMcpServer as Pe,refreshMcpCatalog as Te}from"./mcp.js";import{appendMessage as K,closeSession as xe,createSession as O,deleteSessionFile as b,getActiveSession as C,listSessions as V,loadUserState as q,pruneExpiredSessions as G,resetSession as Ee,saveUserState as y,setActiveSession as Ce}from"./sessionStore.js";import{chunkText as ke,createQueueMap as Re,createSemaphore as be,ensureDir as U,nowMs as _,shortId as Q}from"./utils.js";we();const p=new me(m.telegramToken,{polling:!0}),J=Re(),De=be(m.maxConcurrent),z=new Map;function Le(e,t){z.set(e,t)}function Ne(e){z.delete(e)}function Oe(e){const t=e.trim();if(t){if(t==="Markdown"||t==="MarkdownV2"||t==="HTML")return t;console.warn(`[tg-agent] invalid TELEGRAM_PARSE_MODE=${t}, ignoring`)}}const Ue="image/jpeg";function ze(){return N.join(m.agentDir,"uploads")}function je(e){return e.length===0?null:e.reduce((t,n)=>{const o=t.file_size??t.width*t.height;return(n.file_size??n.width*n.height)>o?n:t})}function Be(e){return!!e?.startsWith("image/")}function Fe(e){switch(N.extname(e).toLowerCase()){case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".webp":return"image/webp";case".gif":return"image/gif";default:return}}function We(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,o=0;for(;n>=1024&&o<t.length-1;)n/=1024,o+=1;return`${n.toFixed(n>=10||o===0?0:1)} ${t[o]}`}function Xe(e){return e.length===0?"":e.map(t=>{const n=[];t.mimeType&&n.push(t.mimeType),t.bytes>0&&n.push(We(t.bytes));const o=n.length>0?` (${n.join(", ")})`:"";return`Attachment saved: ${t.path}${o}`}).join(`
2
- `)}function He(e){const t=[];if(e.photo&&e.photo.length>0){const n=je(e.photo);n&&t.push({kind:"photo",fileId:n.file_id,fileSize:n.file_size})}return e.document&&t.push({kind:"document",fileId:e.document.file_id,fileName:e.document.file_name,mimeType:e.document.mime_type,fileSize:e.document.file_size}),t}async function Ke(e){if(e.length===0)return{resolved:[],images:[]};const t=ze();await U(t);const n=[],o=[];for(const r of e)try{const a=await p.downloadFile(r.fileId,t),s=await W.stat(a),i=r.fileName??N.basename(a),l=r.mimeType??Fe(a),c=r.kind==="photo"||Be(l),u={path:a,name:i,mimeType:l,bytes:s.size,isImage:c};if(n.push(u),c){const f=await W.readFile(a);o.push({type:"image",data:f.toString("base64"),mimeType:l??Ue})}}catch(a){console.warn("[tg-agent] attachment download failed",a)}return{resolved:n,images:o}}const Ve=new Set(["_","*","[","]","(",")","~","`",">","#","+","-","=","|","{","}",".","!"]);function k(e,t){let n=0;for(let o=0;o<e.length;o+=1){const r=e[o];if(r==="\\"){o+=1;continue}r===t&&(n+=1)}return n}function qe(e){if(k(e,"*")%2!==0||k(e,"_")%2!==0||k(e,"`")%2!==0)return!1;const r=k(e,"["),a=k(e,"]");return r===a}function Ge(e){for(let t=0;t<e.length;t+=1){const n=e[t];if(n==="\\"){t+=1;continue}if(Ve.has(n))return!1}return!0}function Y(e){const t=Oe(m.telegramParseMode);return t?[t]:Ge(e)?["MarkdownV2","Markdown"]:qe(e)?["Markdown"]:[]}const Qe=600*1e3,S=new Map,j=new Set,Je=new Map([["codex","openai-codex"],["antigravity","google-antigravity"],["gemini","google-gemini-cli"],["gemini-cli","google-gemini-cli"]]),Ye={"codex-mini-latest":"gpt-5.1-codex-mini","codex-max-latest":"gpt-5.1-codex-max","codex-latest":"gpt-5.2-codex"};function T(e){const t=e.trim().toLowerCase();return Je.get(t)??t}function Ze(e,t){return e!=="openai-codex"?t:Ye[t]??t}async function x(){await U(m.agentDir);const e=fe(m.agentDir),t=Me();t&&e.setRuntimeApiKey("openai-codex",t.accessToken);const n=ge(e,m.agentDir);return{authStorage:e,modelRegistry:n}}function Z(e){const t=S.get(e);t&&(clearTimeout(t.timeoutId),S.delete(e))}function Ie(e,t){const n=S.get(e);n&&(clearTimeout(n.timeoutId),S.delete(e),n.reject(new Error(t)))}async function I(e,t,n){if(S.has(e))throw new Error("Login is already awaiting input.");const o=n.placeholder?`${n.message} (${n.placeholder})`:n.message;return await d(t,o),await new Promise((r,a)=>{const s=setTimeout(()=>{S.delete(e),a(new Error("Login prompt timed out."))},Qe);S.set(e,{resolve:r,reject:a,timeoutId:s,chatId:t})})}async function ee(e,t){const n=z.get(t);if(!n){await d(e,"No active request to stop.");return}n.cancelRequested=!0,n.abortRequested=!0,n.status&&n.status.update("Cancelled by user.",!0),n.abort&&n.abort(),(!n.status||n.chatId!==e)&&await d(e,"Stopping current request...")}function et(){const e={https_proxy:process.env.https_proxy??process.env.HTTPS_PROXY??"",http_proxy:process.env.http_proxy??process.env.HTTP_PROXY??"",all_proxy:process.env.all_proxy??process.env.ALL_PROXY??"",no_proxy:process.env.no_proxy??process.env.NO_PROXY??""},t={NODE_EXTRA_CA_CERTS:process.env.NODE_EXTRA_CA_CERTS??"",NODE_TLS_REJECT_UNAUTHORIZED:process.env.NODE_TLS_REJECT_UNAUTHORIZED??""};console.log(`[tg-agent] modelProvider=${m.modelProvider} modelRef=${m.modelRef} sessionDir=${m.sessionDir} maxConcurrent=${m.maxConcurrent}`),console.log(`[tg-agent] agentDir=${m.agentDir} workspaceDir=${m.workspaceDir}`);const n=Ae(m.agentDir),o=n.length>0?`on (${n.length})`:"off";console.log(`[tg-agent] tools fetchMaxBytes=${m.fetchMaxBytes} fetchTimeoutMs=${m.fetchTimeoutMs} mcp=${o}`),console.log(`[tg-agent] proxy https=${e.https_proxy||"(empty)"} http=${e.http_proxy||"(empty)"} all=${e.all_proxy||"(empty)"} no=${e.no_proxy||"(empty)"}`),console.log(`[tg-agent] tls extra_ca=${t.NODE_EXTRA_CA_CERTS||"(empty)"} reject_unauthorized=${t.NODE_TLS_REJECT_UNAUTHORIZED||"(empty)"}`);try{const{source:a}=he(m.modelProvider);console.log(`[tg-agent] authSource=${a}`)}catch(a){const s=a instanceof Error?a.message:String(a);console.warn(`[tg-agent] authSource=missing (${s})`)}const r=ye();console.log(r?`[tg-agent] proxyUrl=${r.url} kind=${r.kind} source=${r.source}`:"[tg-agent] proxyUrl=(none)")}et(),U(m.agentDir).catch(e=>{console.warn(`[tg-agent] ensure agentDir failed: ${e instanceof Error?e.message:String(e)}`)}),(async()=>{try{await H(m.agentDir,{timeoutMs:3e3,maxBytes:m.fetchMaxBytes,maxChars:2e3}),X()}catch(e){const t=e instanceof Error?e.message:String(e);console.warn(`[tg-agent] mcp catalog warmup failed: ${t}`)}})();const D=$e();console.log(D?`[tg-agent] fetchProxy=${D.url} kind=${D.kind} source=${D.source}`:"[tg-agent] fetchProxy=(none)");function B(e){if(!e.startsWith("/"))return null;const t=e.trim(),[n,...o]=t.split(" ");return{command:n.split("@")[0].slice(1).toLowerCase(),args:o.join(" ").trim()}}async function te(e,t){let n=C(e);return n||(n=O(e,""),await y(e),await d(t,`Created session ${n.id}.`)),n}function tt(e,t){const n=e.getAll(),o=new Map;for(const r of n){const a=o.get(r.provider)??{count:0,auth:t.hasAuth(r.provider)};a.count+=1,o.set(r.provider,a)}return Array.from(o.entries()).sort((r,a)=>r[0].localeCompare(a[0])).map(([r,a])=>({label:`${r} (${a.count}, ${a.auth?"ok":"no auth"})`,command:`/provider ${r}`}))}function nt(e,t,n=[]){const o=e.getAll().filter(i=>i.provider===t).sort((i,l)=>i.id.localeCompare(l.id));if(n.length===0)return o.map(i=>({label:i.id,command:`/model ${t}/${i.id}`}));const r=new Map(o.map(i=>[i.id,i])),a=new Set,s=[];for(const i of n){const l=r.get(i);!l||a.has(l.id)||(a.add(l.id),s.push(l))}for(const i of o)a.has(i.id)||s.push(i);return s.map(i=>({label:i.id,command:`/model ${t}/${i.id}`}))}function ot(e,t,n=3){const o=new Set,r=[],a=V(e);for(const s of a){if(s.modelProvider!==t)continue;const i=s.modelId;if(!(!i||o.has(i))&&(o.add(i),r.push(i),r.length>=n))break}return r}function rt(e){const t=e.trim();if(!t)return null;if(t.includes("/")){const[n,o]=t.split("/",2);return!n||!o?null:{provider:T(n),modelId:o.trim()}}return{modelId:t}}const st=6e4,ne=new Map;function oe(e,t){const n=Date.now(),o=ne.get(e)??0;n-o<st||(ne.set(e,n),console.warn(`[tg-agent] unauthorized user=${e} chat=${t}`))}function re(e){const t=m.telegramAllowedUsers;return!t||t.size===0?!0:t.has(e)}function it(e){return e.length===0?"No sessions found.":["Sessions:",...e.map(n=>{const o=new Date(n.updatedAt).toISOString();return`- ${n.id} | ${n.title} | updated ${o}`})].join(`
3
- `)}async function se(e,t,n){return n?await p.sendMessage(e,t,{parse_mode:n}):await p.sendMessage(e,t)}async function ie(e,t,n,o){if(o){await p.editMessageText(n,{chat_id:e,message_id:t,parse_mode:o});return}await p.editMessageText(n,{chat_id:e,message_id:t})}async function ae(e,t){let n;for(const o of t)try{return await e(o)}catch(r){n=r;const a=r instanceof Error?r.message:String(r);console.warn(`[tg-agent] parse_mode ${o} failed, trying fallback: ${a}`)}try{return await e(void 0)}catch(o){if(n){const r=o instanceof Error?o.message:String(o);console.warn(`[tg-agent] parse_mode fallback to plain failed: ${r}`)}throw o}}async function at(e,t){return await ae(n=>se(e,t,n),Y(t))}async function ct(e,t,n){await ae(o=>ie(e,t,n,o),Y(n))}async function d(e,t){const n=ke(t,3900);for(const o of n)await at(e,o)}const ce=1800*1e3,le=2e3,lt=60,$=new Map;function dt(){const e=_()-ce;for(const[o,r]of $)r.createdAt<e&&$.delete(o);if($.size<=le)return;const t=Array.from($.entries()).sort((o,r)=>o[1].createdAt-r[1].createdAt),n=$.size-le;for(let o=0;o<n;o+=1)$.delete(t[o][0])}function ut(e){dt();let t=Q();for(;$.has(t);)t=Q();return $.set(t,{command:e,createdAt:_()}),t}function mt(e){if(!e.startsWith("cmd:"))return null;const t=e.slice(4),n=$.get(t);return n?_()-n.createdAt>ce?($.delete(t),null):n.command:null}function ft(e,t){if(e.length<=t)return[e];const n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}function gt(e,t){const n=[];let o=[];for(const r of e){const a=ut(r.command);o.push({text:r.label,callback_data:`cmd:${a}`}),o.length>=t&&(n.push(o),o=[])}return o.length>0&&n.push(o),n}async function pt(e,t,n){return await p.sendMessage(e,t,{reply_markup:{inline_keyboard:n}})}async function de(e,t,n,o){const r=o?.perRow??1,a=o?.pageSize??lt,s=o?.footer,i=ft(n,a);for(let l=0;l<i.length;l+=1){const c=i[l],u=i.length>1?` (page ${l+1}/${i.length})`:"",f=[`${t}${u}`];s&&f.push(s);const g=gt(c,r);await pt(e,f.join(`
4
- `),g)}}const wt=3900,ht=1200;function F(e,t=wt){return e.length<=t?{text:e,truncated:!1}:{text:`${e.slice(0,Math.max(0,t-15))}...
1
+ import D from"node:path";import q from"node:fs/promises";import ge from"node-telegram-bot-api";import{discoverAuthStorage as pe,discoverModels as we}from"@mariozechner/pi-coding-agent";import{getOAuthProviders as he}from"@mariozechner/pi-ai";import{config as m,assertConfig as $e}from"./config.js";import{resolveApiKeyForProvider as ye,resolveProxyInfo as Me}from"./auth.js";import{applyFetchProxy as ve}from"./proxy.js";import{runPiAgentPrompt as Se}from"./piAgentRunner.js";import{readCodexOAuth as ke}from"./codexAuth.js";import{formatMcpTarget as Ae,freezeMcpCatalog as G,getMcpCatalog as Q,loadMcpServers as Pe,loadMcpServersSync as _e,probeMcpServer as xe,refreshMcpCatalog as Ce}from"./mcp.js";import{appendMessage as V,closeSession as be,createSession as N,deleteSessionFile as z,getActiveSession as T,listSessions as H,loadChatState as X,pruneExpiredSessions as J,resetSession as Te,saveChatState as h,setActiveSession as Ee,setWorkspaceDir as Re}from"./sessionStore.js";import{chunkText as De,createQueueMap as ze,createSemaphore as Le,ensureDir as j,expandHome as Ue,nowMs as A,shortId as Z}from"./utils.js";$e();const p=new ge(m.telegramToken,{polling:!0}),Y=ze(),Ne=Le(m.maxConcurrent),W=new Map;function je(e,t){W.set(e,t)}function We(e){W.delete(e)}function Oe(e){const t=e.trim();if(t){if(t==="Markdown"||t==="MarkdownV2"||t==="HTML")return t;console.warn(`[tg-agent] invalid TELEGRAM_PARSE_MODE=${t}, ignoring`)}}const Be="image/jpeg";function Fe(){return D.join(m.agentDir,"uploads")}function Ke(e){return e.length===0?null:e.reduce((t,n)=>{const o=t.file_size??t.width*t.height;return(n.file_size??n.width*n.height)>o?n:t})}function qe(e){return!!e?.startsWith("image/")}function Ge(e){switch(D.extname(e).toLowerCase()){case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".webp":return"image/webp";case".gif":return"image/gif";default:return}}function Qe(e){if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB"];let n=e,o=0;for(;n>=1024&&o<t.length-1;)n/=1024,o+=1;return`${n.toFixed(n>=10||o===0?0:1)} ${t[o]}`}function Ve(e){return e.length===0?"":e.map(t=>{const n=[];t.mimeType&&n.push(t.mimeType),t.bytes>0&&n.push(Qe(t.bytes));const o=n.length>0?` (${n.join(", ")})`:"";return`Attachment saved: ${t.path}${o}`}).join(`
2
+ `)}function He(e){const t=[];if(e.photo&&e.photo.length>0){const n=Ke(e.photo);n&&t.push({kind:"photo",fileId:n.file_id,fileSize:n.file_size})}return e.document&&t.push({kind:"document",fileId:e.document.file_id,fileName:e.document.file_name,mimeType:e.document.mime_type,fileSize:e.document.file_size}),t}async function Xe(e){if(e.length===0)return{resolved:[],images:[]};const t=Fe();await j(t);const n=[],o=[];for(const i of e)try{const s=await p.downloadFile(i.fileId,t),d=await q.stat(s),r=i.fileName??D.basename(s),c=i.mimeType??Ge(s),a=i.kind==="photo"||qe(c),u={path:s,name:r,mimeType:c,bytes:d.size,isImage:a};if(n.push(u),a){const f=await q.readFile(s);o.push({type:"image",data:f.toString("base64"),mimeType:c??Be})}}catch(s){console.warn("[tg-agent] attachment download failed",s)}return{resolved:n,images:o}}const Je=new Set(["_","*","[","]","(",")","~","`",">","#","+","-","=","|","{","}",".","!"]);function E(e,t){let n=0;for(let o=0;o<e.length;o+=1){const i=e[o];if(i==="\\"){o+=1;continue}i===t&&(n+=1)}return n}function Ze(e){if(E(e,"*")%2!==0||E(e,"_")%2!==0||E(e,"`")%2!==0)return!1;const i=E(e,"["),s=E(e,"]");return i===s}function Ye(e){for(let t=0;t<e.length;t+=1){const n=e[t];if(n==="\\"){t+=1;continue}if(Je.has(n))return!1}return!0}function I(e){const t=Oe(m.telegramParseMode);return t?[t]:Ye(e)?["MarkdownV2","Markdown"]:Ze(e)?["Markdown"]:[]}const Ie=600*1e3,k=new Map,O=new Set,et=new Map([["codex","openai-codex"],["antigravity","google-antigravity"],["gemini","google-gemini-cli"],["gemini-cli","google-gemini-cli"]]),tt={"codex-mini-latest":"gpt-5.1-codex-mini","codex-max-latest":"gpt-5.1-codex-max","codex-latest":"gpt-5.2-codex"};function _(e){const t=e.trim().toLowerCase();return et.get(t)??t}function nt(e,t){return e!=="openai-codex"?t:tt[t]??t}async function x(){await j(m.agentDir);const e=pe(m.agentDir),t=ke();t&&e.setRuntimeApiKey("openai-codex",t.accessToken);const n=we(e,m.agentDir);return{authStorage:e,modelRegistry:n}}function ee(e){const t=k.get(e);t&&(clearTimeout(t.timeoutId),k.delete(e))}function ot(e,t){const n=k.get(e);n&&(clearTimeout(n.timeoutId),k.delete(e),n.reject(new Error(t)))}async function te(e,t,n){if(k.has(e))throw new Error("Login is already awaiting input.");const o=n.placeholder?`${n.message} (${n.placeholder})`:n.message;return await l(t,o),await new Promise((i,s)=>{const d=setTimeout(()=>{k.delete(e),s(new Error("Login prompt timed out."))},Ie);k.set(e,{resolve:i,reject:s,timeoutId:d,chatId:t})})}async function ne(e){const t=String(e),n=W.get(t);if(!n){await l(e,"No active request to stop.");return}n.cancelRequested=!0,n.abortRequested=!0,n.status&&n.status.update("Cancelled by user.",!0),n.abort&&n.abort(),(!n.status||n.chatId!==e)&&await l(e,"Stopping current request...")}function rt(){console.log(`[tg-agent] modelProvider=${m.modelProvider} modelRef=${m.modelRef} sessionDir=${m.sessionDir} maxConcurrent=${m.maxConcurrent}`),console.log(`[tg-agent] agentDir=${m.agentDir} workspaceDir=${m.workspaceDir}`);const e=_e(m.agentDir),t=e.length>0?`on (${e.length})`:"off";console.log(`[tg-agent] tools fetchMaxBytes=${m.fetchMaxBytes} fetchTimeoutMs=${m.fetchTimeoutMs} mcp=${t}`),console.log(`[tg-agent] proxy url=${m.proxyUrl||"(none)"} fetchProxy=${m.fetchProxyUrl||"(none)"}`),console.log(`[tg-agent] tls extra_ca=${m.tlsExtraCaCerts||"(empty)"} reject_unauthorized=${m.tlsRejectUnauthorized===null?"(unset)":String(m.tlsRejectUnauthorized)}`);try{const{source:o}=ye(m.modelProvider);console.log(`[tg-agent] authSource=${o}`)}catch(o){const i=o instanceof Error?o.message:String(o);console.warn(`[tg-agent] authSource=missing (${i})`)}const n=Me();console.log(n?`[tg-agent] proxyUrl=${n.url} kind=${n.kind} source=${n.source}`:"[tg-agent] proxyUrl=(none)")}rt(),j(m.agentDir).catch(e=>{console.warn(`[tg-agent] ensure agentDir failed: ${e instanceof Error?e.message:String(e)}`)}),(async()=>{try{await Q(m.agentDir,{timeoutMs:3e3,maxBytes:m.fetchMaxBytes,maxChars:2e3}),G()}catch(e){const t=e instanceof Error?e.message:String(e);console.warn(`[tg-agent] mcp catalog warmup failed: ${t}`)}})();const L=ve();console.log(L?`[tg-agent] fetchProxy=${L.url} kind=${L.kind} source=${L.source}`:"[tg-agent] fetchProxy=(none)");function B(e){if(!e.startsWith("/"))return null;const t=e.trim(),[n,...o]=t.split(" ");return{command:n.split("@")[0].slice(1).toLowerCase(),args:o.join(" ").trim()}}async function oe(e,t){let n=T(e);return n||(n=N(e,""),await h(e),await l(t,`Created session ${n.id}.`)),n}function st(e,t){const n=e.getAll(),o=new Map;for(const i of n){const s=o.get(i.provider)??{count:0,auth:t.hasAuth(i.provider)};s.count+=1,o.set(i.provider,s)}return Array.from(o.entries()).sort((i,s)=>i[0].localeCompare(s[0])).map(([i,s])=>({label:`${i} (${s.count}, ${s.auth?"ok":"no auth"})`,command:`/provider ${i}`}))}function it(e,t,n=[]){const o=e.getAll().filter(r=>r.provider===t).sort((r,c)=>r.id.localeCompare(c.id));if(n.length===0)return o.map(r=>({label:r.id,command:`/model ${t}/${r.id}`}));const i=new Map(o.map(r=>[r.id,r])),s=new Set,d=[];for(const r of n){const c=i.get(r);!c||s.has(c.id)||(s.add(c.id),d.push(c))}for(const r of o)s.has(r.id)||d.push(r);return d.map(r=>({label:r.id,command:`/model ${t}/${r.id}`}))}function at(e,t,n=3){const o=new Set,i=[],s=H(e);for(const d of s){if(d.modelProvider!==t)continue;const r=d.modelId;if(!(!r||o.has(r))&&(o.add(r),i.push(r),i.length>=n))break}return i}function ct(e){const t=e.trim();if(!t)return null;if(t.includes("/")){const[n,o]=t.split("/",2);return!n||!o?null:{provider:_(n),modelId:o.trim()}}return{modelId:t}}const lt=6e4,re=new Map;function se(e,t){const n=Date.now(),o=re.get(e)??0;n-o<lt||(re.set(e,n),console.warn(`[tg-agent] unauthorized user=${e} chat=${t}`))}function ie(e){const t=m.telegramAllowedUsers;return!t||t.size===0?!0:t.has(e)}function dt(e){return e.length===0?"No sessions found.":["Sessions:",...e.map(n=>{const o=new Date(n.updatedAt).toISOString();return`- ${n.id} | ${n.title} | updated ${o}`})].join(`
3
+ `)}function F(e,t){if(t.workspaceDir)return t.workspaceDir;const n=m.workspaceMappings.get(e);return n||m.workspaceDir}async function ae(e,t,n){return n?await p.sendMessage(e,t,{parse_mode:n}):await p.sendMessage(e,t)}async function ce(e,t,n,o){if(o){await p.editMessageText(n,{chat_id:e,message_id:t,parse_mode:o});return}await p.editMessageText(n,{chat_id:e,message_id:t})}async function le(e,t){let n;for(const o of t)try{return await e(o)}catch(i){n=i;const s=i instanceof Error?i.message:String(i);console.warn(`[tg-agent] parse_mode ${o} failed, trying fallback: ${s}`)}try{return await e(void 0)}catch(o){if(n){const i=o instanceof Error?o.message:String(o);console.warn(`[tg-agent] parse_mode fallback to plain failed: ${i}`)}throw o}}async function ut(e,t){return await le(n=>ae(e,t,n),I(t))}async function ft(e,t,n){await le(o=>ce(e,t,n,o),I(n))}async function l(e,t){const n=De(t,3900);for(const o of n)await ut(e,o)}const de=1800*1e3,ue=2e3,mt=60,M=new Map;function gt(){const e=A()-de;for(const[o,i]of M)i.createdAt<e&&M.delete(o);if(M.size<=ue)return;const t=Array.from(M.entries()).sort((o,i)=>o[1].createdAt-i[1].createdAt),n=M.size-ue;for(let o=0;o<n;o+=1)M.delete(t[o][0])}function pt(e){gt();let t=Z();for(;M.has(t);)t=Z();return M.set(t,{command:e,createdAt:A()}),t}function wt(e){if(!e.startsWith("cmd:"))return null;const t=e.slice(4),n=M.get(t);return n?A()-n.createdAt>de?(M.delete(t),null):n.command:null}function ht(e,t){if(e.length<=t)return[e];const n=[];for(let o=0;o<e.length;o+=t)n.push(e.slice(o,o+t));return n}function $t(e,t){const n=[];let o=[];for(const i of e){const s=pt(i.command);o.push({text:i.label,callback_data:`cmd:${s}`}),o.length>=t&&(n.push(o),o=[])}return o.length>0&&n.push(o),n}async function yt(e,t,n){return await p.sendMessage(e,t,{reply_markup:{inline_keyboard:n}})}async function fe(e,t,n,o){const i=o?.perRow??1,s=o?.pageSize??mt,d=o?.footer,r=ht(n,s);for(let c=0;c<r.length;c+=1){const a=r[c],u=r.length>1?` (page ${c+1}/${r.length})`:"",f=[`${t}${u}`];d&&f.push(d);const g=$t(a,i);await yt(e,f.join(`
4
+ `),g)}}const Mt=3900,vt=1200;function K(e,t=Mt){return e.length<=t?{text:e,truncated:!1}:{text:`${e.slice(0,Math.max(0,t-15))}...
5
5
 
6
- [truncated]`,truncated:!0}}async function yt(e){const t="Working...",o=(await se(e,t)).message_id;let r=t,a=0,s=Promise.resolve();const i=(c,u=!1)=>{const f=Date.now();!u&&f-a<ht||c!==r&&(r=c,a=f,s=s.then(async()=>{await ie(e,o,c)}).catch(g=>{console.warn("[tg-agent] status edit failed",g)}))},l=c=>{r=c,a=Date.now(),s=s.then(async()=>{await ct(e,o,c)}).catch(u=>{console.warn("[tg-agent] status edit failed",u)})};return{update:(c,u=!1)=>{const f=F(c).text;i(f,u)},finalize:async c=>{const u=F(c).text;l(u),await s},fail:async c=>{const u=F(c).text;l(u),await s}}}function $t(e){switch(e.type){case"agent_start":return"Working...";case"tool_start":return`Running tool: ${e.name}
7
- args: ${vt(e.args)}`;case"tool_end":return e.ok?`Tool finished: ${e.name} (${e.durationMs}ms)`:`Tool failed: ${e.name} (${e.durationMs}ms)`;case"message_start":return e.role==="assistant"?"Generating reply...":null;case"message_end":return e.role==="assistant"?"Finalizing reply...":null;case"heartbeat":return`Working... ${Math.max(1,Math.round(e.elapsedMs/1e3))}s`;default:return null}}function vt(e){try{const t=new WeakSet,n=200,o=12,r=12,a=3,s=(c,u)=>{if(c==null)return c;if(typeof c=="string")return c.length>n?`${c.slice(0,n)}...`:c;if(typeof c=="number"||typeof c=="boolean")return c;if(typeof c=="bigint")return c.toString();if(typeof c=="function")return"[function]";if(typeof c!="object")return String(c);if(t.has(c))return"[circular]";if(u>=a)return"[truncated]";if(t.add(c),Array.isArray(c)){const A=c.slice(0,r).map(w=>s(w,u+1));return c.length>r&&A.push("[truncated]"),A}const f=Object.entries(c),g={};for(const[A,w]of f.slice(0,o))g[A]=s(w,u+1);return f.length>o&&(g._truncated=!0),g},i=s(e,0),l=JSON.stringify(i);return l?l.length>700?`${l.slice(0,700)}...`:l:"null"}catch{return"[unavailable]"}}function L(e){const t=e instanceof Error?e.message:String(e),n=e?.cause;return n instanceof Error?`${t} (${n.message})`:n?`${t} (${String(n)})`:t}function Mt(e,t=140){return e.length<=t?e:`${e.slice(0,t-3)}...`}async function St(){const e=await _e(m.agentDir);if(e.length===0)return"No MCP servers configured. Add [mcp_servers.*] to ~/.tg-agent/config.toml.";const t=await Promise.all(e.map(o=>Pe(o))),n=["MCP servers:"];for(let o=0;o<e.length;o+=1){const r=e[o],a=t[o],s=a.ok?"ok":`error: ${Mt(a.error??"unknown")}`;n.push(`- ${r.name} (${r.type}) ${Se(r)} status=${s} (${a.durationMs}ms)`)}return n.join(`
8
- `)}async function ue(e,t,n,o){const r=await q(t),a=G(r);switch(a.length>0&&(await y(r),await Promise.all(a.map(s=>b(t,s).catch(i=>{console.warn(`[tg-agent] cleanup session file failed id=${s}`,i)})))),n){case"start":case"help":{const s=["Commands:","/new [title] - create a new session","/list - list sessions","/use <id> - switch active session","/close [id] - close a session (default: active)","/reset - clear active session history","/providers - list available providers","/models [provider] - list models for provider","/provider <name> - set provider for current session","/model <provider>/<model> - set model for current session","/mcp - list configured MCP servers","/mcp refresh - reload MCP catalog","/status - show session model settings","/login <provider> - login to OAuth provider","/logout <provider> - logout from provider","/stop - stop the current running request","","Tips:","Send images or files to attach them to the prompt."].join(`
9
- `);await d(e,s);return}case"new":{try{const i=O(r,o||"");await y(r),await d(e,`Created session ${i.id} (${i.title}).`)}catch(s){await d(e,s.message)}return}case"list":{await y(r),await d(e,it(V(r)));return}case"use":{if(!o){await d(e,"Usage: /use <id>");return}if(!Ce(r,o)){await d(e,`Session not found: ${o}`);return}await y(r),await d(e,`Active session set to ${o}.`);return}case"close":{const s=o||r.activeSessionId;if(!s){await d(e,"No active session to close.");return}if(!xe(r,s)){await d(e,`Session not found: ${s}`);return}await y(r),await b(t,s).catch(l=>{console.warn(`[tg-agent] delete session file failed id=${s}`,l)}),await d(e,`Closed session ${s}.`);return}case"reset":{const s=C(r);if(!s){await d(e,"No active session.");return}Ee(s),await y(r),await b(t,s.id).catch(i=>{console.warn(`[tg-agent] reset session file failed id=${s.id}`,i)}),await d(e,`Reset session ${s.id}.`);return}case"providers":{const{authStorage:s,modelRegistry:i}=await x(),l=i.getError(),c=tt(i,s);if(c.length===0){const f=l?`Warning: ${l}
6
+ [truncated]`,truncated:!0}}async function St(e){const t="Working...",o=(await ae(e,t)).message_id;let i=t,s=0,d=Promise.resolve();const r=(a,u=!1)=>{const f=Date.now();!u&&f-s<vt||a!==i&&(i=a,s=f,d=d.then(async()=>{await ce(e,o,a)}).catch(g=>{console.warn("[tg-agent] status edit failed",g)}))},c=a=>{i=a,s=Date.now(),d=d.then(async()=>{await ft(e,o,a)}).catch(u=>{console.warn("[tg-agent] status edit failed",u)})};return{update:(a,u=!1)=>{const f=K(a).text;r(f,u)},finalize:async a=>{const u=K(a).text;c(u),await d},fail:async a=>{const u=K(a).text;c(u),await d}}}function kt(e){switch(e.type){case"agent_start":return"Working...";case"tool_start":return`Running tool: ${e.name}
7
+ args: ${At(e.args)}`;case"tool_end":return e.ok?`Tool finished: ${e.name} (${e.durationMs}ms)`:`Tool failed: ${e.name} (${e.durationMs}ms)`;case"message_start":return e.role==="assistant"?"Generating reply...":null;case"message_end":return e.role==="assistant"?"Finalizing reply...":null;case"heartbeat":return`Working... ${Math.max(1,Math.round(e.elapsedMs/1e3))}s`;default:return null}}function At(e){try{const t=new WeakSet,n=200,o=12,i=12,s=3,d=(a,u)=>{if(a==null)return a;if(typeof a=="string")return a.length>n?`${a.slice(0,n)}...`:a;if(typeof a=="number"||typeof a=="boolean")return a;if(typeof a=="bigint")return a.toString();if(typeof a=="function")return"[function]";if(typeof a!="object")return String(a);if(t.has(a))return"[circular]";if(u>=s)return"[truncated]";if(t.add(a),Array.isArray(a)){const w=a.slice(0,i).map(C=>d(C,u+1));return a.length>i&&w.push("[truncated]"),w}const f=Object.entries(a),g={};for(const[w,C]of f.slice(0,o))g[w]=d(C,u+1);return f.length>o&&(g._truncated=!0),g},r=d(e,0),c=JSON.stringify(r);return c?c.length>700?`${c.slice(0,700)}...`:c:"null"}catch{return"[unavailable]"}}function U(e){const t=e instanceof Error?e.message:String(e),n=e?.cause;return n instanceof Error?`${t} (${n.message})`:n?`${t} (${String(n)})`:t}function Pt(e,t=140){return e.length<=t?e:`${e.slice(0,t-3)}...`}async function _t(){const e=await Pe(m.agentDir);if(e.length===0)return"No MCP servers configured. Add [mcp_servers.*] to ~/.tg-agent/config.toml.";const t=await Promise.all(e.map(o=>xe(o))),n=["MCP servers:"];for(let o=0;o<e.length;o+=1){const i=e[o],s=t[o],d=s.ok?"ok":`error: ${Pt(s.error??"unknown")}`;n.push(`- ${i.name} (${i.type}) ${Ae(i)} status=${d} (${s.durationMs}ms)`)}return n.join(`
8
+ `)}async function me(e,t,n,o){const i=String(e),s=await X(i),d=J(s);switch(d.length>0&&(await h(s),await Promise.all(d.map(r=>z(i,r).catch(c=>{console.warn(`[tg-agent] cleanup session file failed id=${r}`,c)})))),n){case"start":case"help":{const r=["Commands:","/new [title] - create a new session","/list - list sessions","/use <id> - switch active session","/close [id] - close a session (default: active)","/reset - clear active session history","/workspace [path] - view or set workspace directory","/providers - list available providers","/models [provider] - list models for provider","/provider <name> - set provider for current session","/model <provider>/<model> - set model for current session","/mcp - list configured MCP servers","/mcp refresh - reload MCP catalog","/status - show session and workspace settings","/login <provider> - login to OAuth provider","/logout <provider> - logout from provider","/stop - stop the current running request","","Tips:","Send images or files to attach them to the prompt."].join(`
9
+ `);await l(e,r);return}case"new":{try{const c=N(s,o||"");await h(s),await l(e,`Created session ${c.id} (${c.title}).`)}catch(r){await l(e,r.message)}return}case"list":{await h(s),await l(e,dt(H(s)));return}case"use":{if(!o){await l(e,"Usage: /use <id>");return}if(!Ee(s,o)){await l(e,`Session not found: ${o}`);return}await h(s),await l(e,`Active session set to ${o}.`);return}case"close":{const r=o||s.activeSessionId;if(!r){await l(e,"No active session to close.");return}if(!be(s,r)){await l(e,`Session not found: ${r}`);return}await h(s),await z(i,r).catch(a=>{console.warn(`[tg-agent] delete session file failed id=${r}`,a)}),await l(e,`Closed session ${r}.`);return}case"reset":{const r=T(s);if(!r){await l(e,"No active session.");return}Te(r),await h(s),await z(i,r.id).catch(c=>{console.warn(`[tg-agent] reset session file failed id=${r.id}`,c)}),await l(e,`Reset session ${r.id}.`);return}case"workspace":{if(!o){const c=F(i,s),a=s.workspaceDir?"chat":m.workspaceMappings.has(i)?"config":"default";await l(e,`Workspace: ${c} (source: ${a})`);return}const r=D.resolve(Ue(o.trim()));Re(s,r),await h(s),await l(e,`Workspace set to: ${r}`);return}case"providers":{const{authStorage:r,modelRegistry:c}=await x(),a=c.getError(),u=st(c,r);if(u.length===0){const g=a?`Warning: ${a}
10
10
 
11
- No providers found.`:"No providers found.";await d(e,f);return}const u=[];l&&u.push(`Warning: ${l}`),u.push("Providers:"),await de(e,u.join(`
12
- `),c,{perRow:1,footer:"Tap a provider to set it. Use /models <provider> to list models."});return}case"models":{const{authStorage:s,modelRegistry:i}=await x();let l=o?T(o):"";if(!l){const g=C(r);if(!g?.modelProvider){await d(e,"Usage: /models <provider>");return}l=g.modelProvider}const c=s.hasAuth(l),u=ot(r,l),f=nt(i,l,u);if(f.length===0){await d(e,`No models found for provider ${l}.`);return}await de(e,`Models for ${l} (auth: ${c?"ok":"missing"}):`,f,{perRow:1,footer:"Tap a model to set it."});return}case"provider":{if(!o){await d(e,"Usage: /provider <name>");return}const s=T(o),{modelRegistry:i}=await x();if(!i.getAll().some(u=>u.provider===s)){await d(e,`Unknown provider: ${s}`);return}const c=await te(r,e);c.modelProvider=s,c.modelId=void 0,c.updatedAt=_(),await y(r),await d(e,`Session ${c.id} provider set to ${s}.`);return}case"model":{const s=rt(o);if(!s){await d(e,"Usage: /model <provider>/<model>");return}const i=await te(r,e),l=T(s.provider??i.modelProvider??"");if(!l){await d(e,"Usage: /model <provider>/<model>");return}const{modelRegistry:c}=await x(),u=Ze(l,s.modelId);if(!c.find(l,u)){await d(e,`Model not found: ${l}/${u}`);return}i.modelProvider=l,i.modelId=u,i.updatedAt=_(),await y(r),await d(e,`Session ${i.id} model set to ${l}/${u}.`);return}case"status":{const s=C(r);if(!s){await d(e,"No active session.");return}const i=m.modelRef.includes("/")?m.modelRef:`${m.modelProvider}/${m.modelRef}`,l=s.modelProvider||s.modelId?`Session model: ${s.modelProvider??"?"}/${s.modelId??"?"}`:`Default model: ${i}`,c=[`Session: ${s.id} (${s.title})`,l].join(`
13
- `);await d(e,c);return}case"mcp":{if(o.trim().toLowerCase()==="refresh"){Te(),await H(m.agentDir,{timeoutMs:5e3,maxBytes:m.fetchMaxBytes,maxChars:3e3}),X(),await d(e,"MCP catalog refreshed.");return}const s=await St();await d(e,s);return}case"login":{const s=pe();if(!o){const c=s.map(u=>`- ${u.id} (${u.name})`);await d(e,["OAuth providers:",...c].join(`
14
- `));return}const i=T(o);if(j.has(t)){await d(e,"Login already in progress.");return}const l=s.find(c=>c.id===i);if(!l){await d(e,`Unknown OAuth provider: ${i}`);return}j.add(t);try{const{authStorage:c}=await x();await d(e,`Starting login for ${l.id}...`),await c.login(l.id,{onAuth:({url:u,instructions:f})=>{const g=["Open this URL in your browser:",u];f&&g.push("",f),d(e,g.join(`
15
- `))},onPrompt:u=>I(t,e,u),onProgress:u=>{d(e,u)},onManualCodeInput:()=>I(t,e,{message:"Paste the authorization code:"})}),await d(e,`Login completed for ${l.id}.`)}catch(c){const u=L(c);await d(e,`Login failed: ${u}`)}finally{j.delete(t),Z(t)}return}case"logout":{if(!o){await d(e,"Usage: /logout <provider>");return}const s=T(o),{authStorage:i}=await x();i.logout(s),await d(e,`Logged out from ${s}.`);return}default:await d(e,`Unknown command: ${n}`)}}async function _t(e,t,n,o=[]){const r=B(n);if(r){await ue(e,t,r.command,r.args);return}const a=await q(t),s=G(a);s.length>0&&(await y(a),await Promise.all(s.map(h=>b(t,h).catch(P=>{console.warn(`[tg-agent] cleanup session file failed id=${h}`,P)}))));let i=C(a);if(!i)try{i=O(a,""),await y(a),await d(e,`Created session ${i.id}.`)}catch(h){await d(e,h.message);return}const{resolved:l,images:c}=await Ke(o);if(o.length>0&&l.length===0&&!n.trim()){await d(e,"Failed to download attachments.");return}const u=Xe(l),f=[];n.trim()&&f.push(n.trim()),u&&f.push(u);const g=f.join(`
11
+ No providers found.`:"No providers found.";await l(e,g);return}const f=[];a&&f.push(`Warning: ${a}`),f.push("Providers:"),await fe(e,f.join(`
12
+ `),u,{perRow:1,footer:"Tap a provider to set it. Use /models <provider> to list models."});return}case"models":{const{authStorage:r,modelRegistry:c}=await x();let a=o?_(o):"";if(!a){const w=T(s);if(!w?.modelProvider){await l(e,"Usage: /models <provider>");return}a=w.modelProvider}const u=r.hasAuth(a),f=at(s,a),g=it(c,a,f);if(g.length===0){await l(e,`No models found for provider ${a}.`);return}await fe(e,`Models for ${a} (auth: ${u?"ok":"missing"}):`,g,{perRow:1,footer:"Tap a model to set it."});return}case"provider":{if(!o){await l(e,"Usage: /provider <name>");return}const r=_(o),{modelRegistry:c}=await x();if(!c.getAll().some(f=>f.provider===r)){await l(e,`Unknown provider: ${r}`);return}const u=await oe(s,e);u.modelProvider=r,u.modelId=void 0,u.updatedAt=A(),await h(s),await l(e,`Session ${u.id} provider set to ${r}.`);return}case"model":{const r=ct(o);if(!r){await l(e,"Usage: /model <provider>/<model>");return}const c=await oe(s,e),a=_(r.provider??c.modelProvider??"");if(!a){await l(e,"Usage: /model <provider>/<model>");return}const{modelRegistry:u}=await x(),f=nt(a,r.modelId);if(!u.find(a,f)){await l(e,`Model not found: ${a}/${f}`);return}c.modelProvider=a,c.modelId=f,c.updatedAt=A(),await h(s),await l(e,`Session ${c.id} model set to ${a}/${f}.`);return}case"status":{const r=T(s);if(!r){await l(e,"No active session.");return}const c=m.modelRef.includes("/")?m.modelRef:`${m.modelProvider}/${m.modelRef}`,a=r.modelProvider||r.modelId?`Model: ${r.modelProvider??"?"}/${r.modelId??"?"}`:`Model: ${c} (default)`,u=F(i,s),f=s.workspaceDir?"chat":m.workspaceMappings.has(i)?"config":"default",g=[`Session: ${r.id} (${r.title})`,a,`Workspace: ${u} (${f})`].join(`
13
+ `);await l(e,g);return}case"mcp":{if(o.trim().toLowerCase()==="refresh"){Ce(),await Q(m.agentDir,{timeoutMs:5e3,maxBytes:m.fetchMaxBytes,maxChars:3e3}),G(),await l(e,"MCP catalog refreshed.");return}const r=await _t();await l(e,r);return}case"login":{const r=he();if(!o){const u=r.map(f=>`- ${f.id} (${f.name})`);await l(e,["OAuth providers:",...u].join(`
14
+ `));return}const c=_(o);if(O.has(t)){await l(e,"Login already in progress.");return}const a=r.find(u=>u.id===c);if(!a){await l(e,`Unknown OAuth provider: ${c}`);return}O.add(t);try{const{authStorage:u}=await x();await l(e,`Starting login for ${a.id}...`),await u.login(a.id,{onAuth:({url:f,instructions:g})=>{const w=["Open this URL in your browser:",f];g&&w.push("",g),l(e,w.join(`
15
+ `))},onPrompt:f=>te(t,e,f),onProgress:f=>{l(e,f)},onManualCodeInput:()=>te(t,e,{message:"Paste the authorization code:"})}),await l(e,`Login completed for ${a.id}.`)}catch(u){const f=U(u);await l(e,`Login failed: ${f}`)}finally{O.delete(t),ee(t)}return}case"logout":{if(!o){await l(e,"Usage: /logout <provider>");return}const r=_(o),{authStorage:c}=await x();c.logout(r),await l(e,`Logged out from ${r}.`);return}default:await l(e,`Unknown command: ${n}`)}}async function xt(e,t,n,o=[]){const i=B(n);if(i){await me(e,t,i.command,i.args);return}const s=String(e),d=await X(s),r=J(d);r.length>0&&(await h(d),await Promise.all(r.map($=>z(s,$).catch(P=>{console.warn(`[tg-agent] cleanup session file failed id=${$}`,P)}))));let c=T(d);if(!c)try{c=N(d,""),await h(d),await l(e,`Created session ${c.id}.`)}catch($){await l(e,$.message);return}const{resolved:a,images:u}=await Xe(o);if(o.length>0&&a.length===0&&!n.trim()){await l(e,"Failed to download attachments.");return}const f=Ve(a),g=[];n.trim()&&g.push(n.trim()),f&&g.push(f);const w=g.join(`
16
16
 
17
- `)||"Attachment received.",A={role:"user",content:g,ts:_()};K(i,A,m.maxHistoryMessages),await y(a),console.log(`[tg-agent] request user=${t} session=${i.id} messages=${i.messages.length} textLen=${g.length} attachments=${l.length} provider=${i.modelProvider??"-"} model=${i.modelId??"-"}`);let w=null;try{w=await yt(e)}catch(h){console.warn("[tg-agent] status message failed",h)}const E={sessionId:i.id,chatId:e,status:w,abortRequested:!1,cancelRequested:!1};Le(t,E);try{const h=await De(async()=>{const R=await ve({userId:t,sessionId:i.id,prompt:g,images:c,systemPrompt:m.systemPrompt,modelProvider:i.modelProvider,modelId:i.modelId,telegram:{chatId:e,sendPhoto:async(v,M)=>{await p.sendPhoto(e,v,M?{caption:M}:void 0)},sendDocument:async(v,M)=>{await p.sendDocument(e,v,M?{caption:M}:void 0)}},onAbortReady:v=>{E.abort=v,E.abortRequested&&v()},onStatus:w?v=>{const M=$t(v);M&&w?.update(M)}:void 0});return console.log(`[tg-agent] response session=${i.id} model=${R.modelProvider}/${R.modelId} file=${R.sessionFile}`),R.text});if(E.cancelRequested){w?await w.finalize("Cancelled by user."):await d(e,"Cancelled by user.");return}const P={role:"assistant",content:h,ts:_()};K(i,P,m.maxHistoryMessages),await y(a),w?await w.finalize(h):await d(e,h)}catch(h){if(console.error("[tg-agent] runModel error",h),E.cancelRequested){w?await w.fail("Cancelled by user."):await d(e,"Cancelled by user.");return}const P=L(h);w?await w.fail(`Error: ${P}`):await d(e,`Error: ${P}`)}finally{Ne(t)}}p.on("callback_query",e=>{const t=e.data??"",n=e.message?.chat.id;if(!e.from?.id||!n){p.answerCallbackQuery(e.id);return}const o=String(e.from.id);if(!re(o)){oe(o,n),p.answerCallbackQuery(e.id);return}if(!t.startsWith("cmd:")){p.answerCallbackQuery(e.id);return}const r=mt(t);if(!r){p.answerCallbackQuery(e.id,{text:"Command expired."});return}p.answerCallbackQuery(e.id),J(o,async()=>{const a=B(r);if(!a){await d(n,"Invalid command.");return}if(a.command==="stop"){await ee(n,o);return}await ue(n,o,a.command,a.args)}).catch(async a=>{console.error("[tg-agent] runModel error",a);const s=L(a);await d(n,`Error: ${s}`)})}),p.on("message",e=>{if(e.chat.type!=="private"){p.sendMessage(e.chat.id,"This bot only supports direct messages.");return}if(!e.from?.id)return;const t=String(e.from.id),n=e.chat.id;if(!re(t)){oe(t,n);return}const o=(e.text??e.caption??"").trim(),r=He(e);if(!o&&r.length===0)return;const a=S.get(t);if(a){if(o==="/stop"||o==="/cancel"){Ie(t,"Login cancelled by user."),d(n,"Login cancelled.");return}if(!o){d(n,"Login expects a text response.");return}Z(t),a.resolve(o);return}if(B(o)?.command==="stop"){ee(n,t);return}J(t,()=>_t(n,t,o,r)).catch(async i=>{console.error("[tg-agent] runModel error",i);const l=L(i);await d(n,`Error: ${l}`)})}),p.on("polling_error",e=>{console.error("Polling error",e)}),console.log("tg-agent started");
17
+ `)||"Attachment received.",C={role:"user",content:w,ts:A()};V(c,C,m.maxHistoryMessages),await h(d),console.log(`[tg-agent] request user=${t} chat=${s} session=${c.id} messages=${c.messages.length} textLen=${w.length} attachments=${a.length} provider=${c.modelProvider??"-"} model=${c.modelId??"-"}`);let y=null;try{y=await St(e)}catch($){console.warn("[tg-agent] status message failed",$)}const b={sessionId:c.id,chatId:e,status:y,abortRequested:!1,cancelRequested:!1};je(s,b);try{const $=await Ne(async()=>{const R=await Se({chatId:s,sessionId:c.id,prompt:w,images:u,systemPrompt:m.systemPrompt,modelProvider:c.modelProvider,modelId:c.modelId,workspaceDir:F(s,d),telegram:{chatId:e,sendPhoto:async(v,S)=>{await p.sendPhoto(e,v,S?{caption:S}:void 0)},sendDocument:async(v,S)=>{await p.sendDocument(e,v,S?{caption:S}:void 0)}},onAbortReady:v=>{b.abort=v,b.abortRequested&&v()},onStatus:y?v=>{const S=kt(v);S&&y?.update(S)}:void 0});return console.log(`[tg-agent] response session=${c.id} model=${R.modelProvider}/${R.modelId} file=${R.sessionFile}`),R.text});if(b.cancelRequested){y?await y.finalize("Cancelled by user."):await l(e,"Cancelled by user.");return}const P={role:"assistant",content:$,ts:A()};V(c,P,m.maxHistoryMessages),await h(d),y?await y.finalize($):await l(e,$)}catch($){if(console.error("[tg-agent] runModel error",$),b.cancelRequested){y?await y.fail("Cancelled by user."):await l(e,"Cancelled by user.");return}const P=U($);y?await y.fail(`Error: ${P}`):await l(e,`Error: ${P}`)}finally{We(s)}}p.on("callback_query",e=>{const t=e.data??"",n=e.message?.chat.id;if(!e.from?.id||!n){p.answerCallbackQuery(e.id);return}const o=String(e.from.id);if(!ie(o)){se(o,n),p.answerCallbackQuery(e.id);return}if(!t.startsWith("cmd:")){p.answerCallbackQuery(e.id);return}const i=wt(t);if(!i){p.answerCallbackQuery(e.id,{text:"Command expired."});return}p.answerCallbackQuery(e.id);const s=String(n);Y(s,async()=>{const d=B(i);if(!d){await l(n,"Invalid command.");return}if(d.command==="stop"){await ne(n);return}await me(n,o,d.command,d.args)}).catch(async d=>{console.error("[tg-agent] runModel error",d);const r=U(d);await l(n,`Error: ${r}`)})}),p.on("message",e=>{if(!e.from?.id)return;const t=String(e.from.id),n=e.chat.id,o=String(n);if(!ie(t)){se(t,n);return}const i=(e.text??e.caption??"").trim(),s=He(e);if(!i&&s.length===0)return;const d=k.get(t);if(d){if(i==="/stop"||i==="/cancel"){ot(t,"Login cancelled by user."),l(n,"Login cancelled.");return}if(!i){l(n,"Login expects a text response.");return}ee(t),d.resolve(i);return}if(B(i)?.command==="stop"){ne(n);return}Y(o,()=>xt(n,t,i,s)).catch(async c=>{console.error("[tg-agent] runModel error",c);const a=U(c);await l(n,`Error: ${a}`)})}),p.on("polling_error",e=>{console.error("Polling error",e)}),console.log("tg-agent started");
package/dist/mcp.js CHANGED
@@ -1,5 +1,5 @@
1
- import R from"node:fs";import H from"node:fs/promises";import q from"node:path";import J from"node:readline";import{spawn as U}from"node:child_process";const G=6e4,A=2e5,Q=40,X=3500;let w=null,P=!1;const h=new Map,V=1500;function z(t){return q.join(t,"config.toml")}function dt(t){const e=z(t);try{const r=R.readFileSync(e,"utf8");return O(r)}catch{return[]}}async function Y(t){const e=z(t);try{const r=await H.readFile(e,"utf8");return O(r)}catch{return[]}}async function pt(t,e={}){if(w)return w;const r=await Y(t);if(r.length===0)return w="","";const n=e.maxTools??Q,s=e.maxChars??X,i=e.timeoutMs??4e3,o=e.maxBytes??A,a=["MCP catalog (auto-discovered):","Use tool 'mcp' with server=<name> method=<tool> params=<object>."];for(const u of r){const l=await K(u,{timeoutMs:i,maxBytes:o});if(a.push(`${u.name} (${u.type})`),l.length===0){a.push(" - tools unavailable");continue}const d=l.slice(0,n);for(const p of d){const y=p.description?`: ${p.description}`:"";a.push(` - ${p.name}${y}`)}l.length>d.length&&a.push(` - ...and ${l.length-d.length} more`)}let c=a.join(`
2
- `);return c.length>s&&(c=`${c.slice(0,s-20)}...
3
- (truncated)`),w=c,c}function mt(){w&&(P=!0)}function ht(){w=null,P=!1,h.clear()}function gt(t){if(t.type==="http")return t.url?`url=${t.url}`:"url=(missing)";const e=t.args&&t.args.length>0?` ${t.args.join(" ")}`:"";return t.command?`command=${t.command}${e}`:"command=(missing)"}async function yt(t,e=5e3){const r=Date.now();try{return await $(t,"tools/list",{},{timeoutMs:e}),{ok:!0,durationMs:Date.now()-r}}catch(n){const s=n instanceof Error?n.message:String(n);return{ok:!1,durationMs:Date.now()-r,error:s}}}async function $(t,e,r,n={}){const s=j(n.timeoutMs??G,1e3,12e4),i=j(n.maxBytes??A,1024,5e6),o=new AbortController,a=()=>o.abort();n.signal?.addEventListener("abort",a,{once:!0});const c=setTimeout(()=>o.abort(),s);try{return t.type==="http"?(e!=="initialize"&&e!=="notifications/initialized"&&await v(t,i,o.signal),await B(t,e,r,i,o.signal)):await nt(t,e,r,i,o.signal)}finally{clearTimeout(c),n.signal?.removeEventListener("abort",a)}}function j(t,e,r){return Number.isNaN(t)?e:Math.min(r,Math.max(e,t))}async function K(t,e){try{const r=await $(t,"tools/list",{},e);return Z(r.output)}catch(r){const n=r instanceof Error?r.message:String(r);return console.warn(`[tg-agent] mcp tools/list failed server=${t.name} error=${n}`),[]}}function Z(t){let e;try{e=JSON.parse(t)}catch{return[]}if(!e||typeof e!="object")return[];const r=e,s=(r.result??r).tools??r.tools;if(!Array.isArray(s))return[];const i=[];for(const o of s){if(!o||typeof o!="object")continue;const a=o,c=typeof a.name=="string"?a.name:"";if(!c)continue;const u=typeof a.description=="string"?a.description:void 0,l=a.inputSchema;i.push({name:c,description:u,inputSchema:l})}return i}async function B(t,e,r,n,s){if(!t.url)throw new Error("MCP server url is missing.");const i=Date.now(),o=await k(t,e,r,n,s),a=I(o.parsed);if(a)throw new Error(a);let c=o.bodyText;return o.parsed&&(c=JSON.stringify(o.parsed,null,2)),{ok:o.statusCode>=200&&o.statusCode<300,output:c,durationMs:Date.now()-i,bytes:o.bytes,truncated:o.truncated,statusCode:o.statusCode,statusText:o.statusText,contentType:o.contentType}}async function k(t,e,r,n,s){if(!t.url)throw new Error("MCP server url is missing.");const i=h.get(t.name),o={jsonrpc:"2.0",id:`tg-agent-${Date.now()}`,method:e,params:r??{}},a={"content-type":"application/json"};t.auth&&(a.authorization=t.auth.startsWith("Bearer ")?t.auth:`Bearer ${t.auth}`),i?.cookie&&(a.cookie=i.cookie),i?.sessionId&&(a["mcp-session-id"]=i.sessionId);const c=await fetch(t.url,{method:"POST",headers:a,body:JSON.stringify(o),signal:s});tt(t.name,c);const u=await ot(c,n,s);let l;try{l=JSON.parse(u.text)}catch{l=void 0}return et(t.name,l),{statusCode:c.status,statusText:c.statusText,contentType:c.headers.get("content-type"),bodyText:u.text,parsed:l,bytes:u.bytes,truncated:u.truncated}}function I(t){if(!t||typeof t!="object")return null;const e=t;if(e.error&&typeof e.error=="object"){const r=e.error;if(typeof r.message=="string")return r.message}return typeof e.error_description=="string"?e.error_description:typeof e.error=="string"?e.error:null}async function v(t,e,r){const n=h.get(t.name);if(n?.initializedAt)return;if(n?.inFlight){await n.inFlight;return}const s=(async()=>{const i=await k(t,"initialize",{protocolVersion:"2024-11-05",clientInfo:{name:"tg-agent",version:process.env.npm_package_version??"dev"},capabilities:{}},e,r),o=I(i.parsed);if(o){const u=o.toLowerCase();if(u.includes("method")&&u.includes("not"))return;throw new Error(o)}const a=new AbortController,c=setTimeout(()=>a.abort(),V);try{await k(t,"notifications/initialized",{},e,a.signal)}catch{}finally{clearTimeout(c)}})();h.set(t.name,{initializedAt:Date.now(),inFlight:s});try{await s;const i=h.get(t.name);h.set(t.name,{initializedAt:Date.now(),cookie:i?.cookie,sessionId:i?.sessionId})}catch(i){throw h.delete(t.name),i}}function tt(t,e){const n=h.get(t)??{initializedAt:0},s=e.headers.get("set-cookie"),i=e.headers.getSetCookie?.(),o=i&&i.length>0?i.join("; "):s;o&&h.set(t,{...n,cookie:o});const a=e.headers.get("mcp-session-id")??e.headers.get("x-mcp-session-id");a&&h.set(t,{...n,sessionId:a})}function et(t,e){if(!e||typeof e!="object")return;const n=e.result;if(!n)return;const s=typeof n.sessionId=="string"&&n.sessionId||typeof n.session_id=="string"&&n.session_id;if(!s)return;const i=h.get(t)??{initializedAt:0};h.set(t,{...i,sessionId:s})}async function nt(t,e,r,n,s){if(!t.command)throw new Error("MCP server command is missing.");const i=Date.now(),o=U(t.command,t.args??[],{stdio:["pipe","pipe","pipe"]}),a=()=>{o.killed||o.kill("SIGTERM")};if(s.aborted)throw a(),new Error("MCP request aborted.");const c=new Map;let u=!1,l="";const d=new Error("MCP request aborted."),p=J.createInterface({input:o.stdout,terminal:!1});p.on("line",f=>{if(f.length>n){u=!0;return}let m=null;try{m=JSON.parse(f)}catch{return}const g=typeof m.id=="string"?m.id:typeof m.id=="number"?String(m.id):"";if(!g)return;const x=c.get(g);x&&(c.delete(g),x.resolve(m))}),o.stderr?.on("data",f=>{const m=f.toString();if(l.length+m.length>n){u=!0;return}l+=m});const y=()=>{for(const f of c.values())f.reject(d);c.clear(),a()};s.addEventListener("abort",y,{once:!0});const b=f=>{const m=f===0?"MCP stdio exited.":l.trim()||"MCP stdio exited with error.";for(const g of c.values())g.reject(new Error(m));c.clear()};o.once("exit",f=>b(f));const _=async(f,m)=>{const g=`tg-agent-${Date.now()}-${Math.random().toString(16).slice(2,8)}`,x={jsonrpc:"2.0",id:g,method:f,params:m??{}};return await new Promise((W,N)=>{c.set(g,{resolve:W,reject:N}),o.stdin?.write(`${JSON.stringify(x)}
4
- `)})},S=await _("initialize",{protocolVersion:"2024-11-05",clientInfo:{name:"tg-agent",version:process.env.npm_package_version??"dev"},capabilities:{}});if(S.error&&typeof S.error=="object"){const f=S.error.message??"MCP initialize failed.";throw a(),new Error(String(f))}const L={jsonrpc:"2.0",method:"notifications/initialized"};o.stdin?.write(`${JSON.stringify(L)}
5
- `);const T=await _(e,r);s.removeEventListener("abort",y),p.close(),o.stdin?.end(),a();const D=new Promise(f=>{o.once("exit",()=>f())});if(await Promise.race([D,new Promise(f=>setTimeout(f,500))]),T.error&&typeof T.error=="object"){const f=T.error.message??"MCP request failed.";throw new Error(String(f))}const M=JSON.stringify(T,null,2),F=M.length>n?M.slice(0,n):M;return M.length>n&&(u=!0),{ok:!0,output:F,durationMs:Date.now()-i,bytes:Math.min(M.length,n),truncated:u}}async function ot(t,e,r){const n=t.body?.getReader?.();if(!n){const d=await t.text(),p=Math.min(d.length,e);return{text:d.slice(0,e),bytes:p,truncated:d.length>e}}const s=new TextDecoder("utf-8"),i=[];let o=0,a=!1;for(;;){if(r.aborted){try{await n.cancel()}catch{}throw new Error("MCP request aborted.")}const{done:d,value:p}=await n.read();if(d)break;if(!p)continue;const y=o+p.byteLength;if(y>e){const b=Math.max(0,e-o);b>0&&(i.push(p.slice(0,b)),o+=b),a=!0;try{await n.cancel()}catch{}break}i.push(p),o=y}const c=new Uint8Array(o);let u=0;for(const d of i)c.set(d,u),u+=d.byteLength;return{text:s.decode(c),bytes:o,truncated:a}}function O(t){const e=[];let r=null,n={};const s=()=>{if(!r)return;const i=n.type??(n.url?"http":n.command?"stdio":void 0);if(!i)return;const o=i==="http"||i==="stdio"?i:void 0;o&&(o==="http"&&!n.url||o==="stdio"&&!n.command||e.push({name:r,type:o,url:n.url,command:n.command,args:n.args,auth:n.auth}))};for(const i of t.split(/\r?\n/)){const o=it(i).trim();if(!o)continue;if(o.startsWith("[")&&o.endsWith("]")){s();const l=o.slice(1,-1).trim();if(!l.startsWith("mcp_servers.")){r=null,n={};continue}let d=l.slice(12).trim();if(d=E(d),!d){r=null,n={};continue}r=d,n={};continue}if(!r)continue;const a=o.indexOf("=");if(a===-1)continue;const c=o.slice(0,a).trim(),u=o.slice(a+1).trim();if(c)switch(c){case"type":n.type=C(u).toLowerCase();break;case"url":n.url=C(u);break;case"command":n.command=C(u);break;case"args":n.args=rt(u);break;case"auth":case"authorization":case"token":n.auth=C(u);break;default:break}}return s(),e}function E(t){return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function C(t){const e=t.trim();return e?e.startsWith('"')&&e.endsWith('"')?e.slice(1,-1).replace(/\\\\/g,"\\").replace(/\\"/g,'"'):e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e:""}function rt(t){const e=t.trim();if(!e.startsWith("[")||!e.endsWith("]"))return[];const r=e.slice(1,-1).trim();if(!r)return[];const n=[];let s="",i=!1,o="";for(let c=0;c<r.length;c+=1){const u=r[c];if(!i&&(u==='"'||u==="'")){i=!0,o=u;continue}if(i&&u===o){i=!1;continue}if(!i&&u===","){const l=s.trim();l&&n.push(E(l)),s="";continue}s+=u}const a=s.trim();return a&&n.push(E(a)),n}function it(t){let e=!1,r="";for(let n=0;n<t.length;n+=1){const s=t[n];if(!e&&(s==='"'||s==="'")){e=!0,r=s;continue}if(e&&s===r){e=!1;continue}if(!e&&(s==="#"||s===";"))return t.slice(0,n)}return t}export{$ as callMcpServer,gt as formatMcpTarget,mt as freezeMcpCatalog,pt as getMcpCatalog,z as getMcpConfigPath,Y as loadMcpServers,dt as loadMcpServersSync,yt as probeMcpServer,ht as refreshMcpCatalog};
1
+ import J from"node:fs";import R from"node:fs/promises";import W from"node:path";import q from"node:readline";import{spawn as H}from"node:child_process";import*as U from"@iarna/toml";const G=6e4,A=2e5,Q=40,X=3500;let w=null,_=!1;const m=new Map,V=1500;function I(t){return W.join(t,"config.toml")}function lt(t){const e=I(t);try{const r=J.readFileSync(e,"utf8");return O(r)}catch{return[]}}async function Y(t){const e=I(t);try{const r=await R.readFile(e,"utf8");return O(r)}catch{return[]}}async function ft(t,e={}){if(w)return w;const r=await Y(t);if(r.length===0)return w="","";const o=e.maxTools??Q,a=e.maxChars??X,i=e.timeoutMs??4e3,n=e.maxBytes??A,s=["MCP catalog (auto-discovered):","Use tool 'mcp' with server=<name> method=<tool> params=<object>."];for(const u of r){const l=await B(u,{timeoutMs:i,maxBytes:n});if(s.push(`${u.name} (${u.type})`),l.length===0){s.push(" - tools unavailable");continue}const d=l.slice(0,o);for(const p of d){const g=p.description?`: ${p.description}`:"";s.push(` - ${p.name}${g}`)}l.length>d.length&&s.push(` - ...and ${l.length-d.length} more`)}let c=s.join(`
2
+ `);return c.length>a&&(c=`${c.slice(0,a-20)}...
3
+ (truncated)`),w=c,c}function dt(){w&&(_=!0)}function pt(){w=null,_=!1,m.clear()}function ht(t){if(t.type==="http")return t.url?`url=${t.url}`:"url=(missing)";const e=t.args&&t.args.length>0?` ${t.args.join(" ")}`:"";return t.command?`command=${t.command}${e}`:"command=(missing)"}async function mt(t,e=5e3){const r=Date.now();try{return await P(t,"tools/list",{},{timeoutMs:e}),{ok:!0,durationMs:Date.now()-r}}catch(o){const a=o instanceof Error?o.message:String(o);return{ok:!1,durationMs:Date.now()-r,error:a}}}async function P(t,e,r,o={}){const a=k(o.timeoutMs??G,1e3,12e4),i=k(o.maxBytes??A,1024,5e6),n=new AbortController,s=()=>n.abort();o.signal?.addEventListener("abort",s,{once:!0});const c=setTimeout(()=>n.abort(),a);try{return t.type==="http"?(e!=="initialize"&&e!=="notifications/initialized"&&await v(t,i,n.signal),await Z(t,e,r,i,n.signal)):await nt(t,e,r,i,n.signal)}finally{clearTimeout(c),o.signal?.removeEventListener("abort",s)}}function k(t,e,r){return Number.isNaN(t)?e:Math.min(r,Math.max(e,t))}async function B(t,e){try{const r=await P(t,"tools/list",{},e);return K(r.output)}catch(r){const o=r instanceof Error?r.message:String(r);return console.warn(`[tg-agent] mcp tools/list failed server=${t.name} error=${o}`),[]}}function K(t){let e;try{e=JSON.parse(t)}catch{return[]}if(!e||typeof e!="object")return[];const r=e,a=(r.result??r).tools??r.tools;if(!Array.isArray(a))return[];const i=[];for(const n of a){if(!n||typeof n!="object")continue;const s=n,c=typeof s.name=="string"?s.name:"";if(!c)continue;const u=typeof s.description=="string"?s.description:void 0,l=s.inputSchema;i.push({name:c,description:u,inputSchema:l})}return i}async function Z(t,e,r,o,a){if(!t.url)throw new Error("MCP server url is missing.");const i=Date.now(),n=await x(t,e,r,o,a),s=j(n.parsed);if(s)throw new Error(s);let c=n.bodyText;return n.parsed&&(c=JSON.stringify(n.parsed,null,2)),{ok:n.statusCode>=200&&n.statusCode<300,output:c,durationMs:Date.now()-i,bytes:n.bytes,truncated:n.truncated,statusCode:n.statusCode,statusText:n.statusText,contentType:n.contentType}}async function x(t,e,r,o,a){if(!t.url)throw new Error("MCP server url is missing.");const i=m.get(t.name),n={jsonrpc:"2.0",id:`tg-agent-${Date.now()}`,method:e,params:r??{}},s={"content-type":"application/json"};t.auth&&(s.authorization=t.auth.startsWith("Bearer ")?t.auth:`Bearer ${t.auth}`),i?.cookie&&(s.cookie=i.cookie),i?.sessionId&&(s["mcp-session-id"]=i.sessionId);const c=await fetch(t.url,{method:"POST",headers:s,body:JSON.stringify(n),signal:a});tt(t.name,c);const u=await ot(c,o,a);let l;try{l=JSON.parse(u.text)}catch{l=void 0}return et(t.name,l),{statusCode:c.status,statusText:c.statusText,contentType:c.headers.get("content-type"),bodyText:u.text,parsed:l,bytes:u.bytes,truncated:u.truncated}}function j(t){if(!t||typeof t!="object")return null;const e=t;if(e.error&&typeof e.error=="object"){const r=e.error;if(typeof r.message=="string")return r.message}return typeof e.error_description=="string"?e.error_description:typeof e.error=="string"?e.error:null}async function v(t,e,r){const o=m.get(t.name);if(o?.initializedAt)return;if(o?.inFlight){await o.inFlight;return}const a=(async()=>{const i=await x(t,"initialize",{protocolVersion:"2024-11-05",clientInfo:{name:"tg-agent",version:process.env.npm_package_version??"dev"},capabilities:{}},e,r),n=j(i.parsed);if(n){const u=n.toLowerCase();if(u.includes("method")&&u.includes("not"))return;throw new Error(n)}const s=new AbortController,c=setTimeout(()=>s.abort(),V);try{await x(t,"notifications/initialized",{},e,s.signal)}catch{}finally{clearTimeout(c)}})();m.set(t.name,{initializedAt:Date.now(),inFlight:a});try{await a;const i=m.get(t.name);m.set(t.name,{initializedAt:Date.now(),cookie:i?.cookie,sessionId:i?.sessionId})}catch(i){throw m.delete(t.name),i}}function tt(t,e){const o=m.get(t)??{initializedAt:0},a=e.headers.get("set-cookie"),i=e.headers.getSetCookie?.(),n=i&&i.length>0?i.join("; "):a;n&&m.set(t,{...o,cookie:n});const s=e.headers.get("mcp-session-id")??e.headers.get("x-mcp-session-id");s&&m.set(t,{...o,sessionId:s})}function et(t,e){if(!e||typeof e!="object")return;const o=e.result;if(!o)return;const a=typeof o.sessionId=="string"&&o.sessionId||typeof o.session_id=="string"&&o.session_id;if(!a)return;const i=m.get(t)??{initializedAt:0};m.set(t,{...i,sessionId:a})}async function nt(t,e,r,o,a){if(!t.command)throw new Error("MCP server command is missing.");const i=Date.now(),n=H(t.command,t.args??[],{stdio:["pipe","pipe","pipe"]}),s=()=>{n.killed||n.kill("SIGTERM")};if(a.aborted)throw s(),new Error("MCP request aborted.");const c=new Map;let u=!1,l="";const d=new Error("MCP request aborted."),p=q.createInterface({input:n.stdout,terminal:!1});p.on("line",f=>{if(f.length>o){u=!0;return}let h=null;try{h=JSON.parse(f)}catch{return}const y=typeof h.id=="string"?h.id:typeof h.id=="number"?String(h.id):"";if(!y)return;const C=c.get(y);C&&(c.delete(y),C.resolve(h))}),n.stderr?.on("data",f=>{const h=f.toString();if(l.length+h.length>o){u=!0;return}l+=h});const g=()=>{for(const f of c.values())f.reject(d);c.clear(),s()};a.addEventListener("abort",g,{once:!0});const b=f=>{const h=f===0?"MCP stdio exited.":l.trim()||"MCP stdio exited with error.";for(const y of c.values())y.reject(new Error(h));c.clear()};n.once("exit",f=>b(f));const E=async(f,h)=>{const y=`tg-agent-${Date.now()}-${Math.random().toString(16).slice(2,8)}`,C={jsonrpc:"2.0",id:y,method:f,params:h??{}};return await new Promise((F,N)=>{c.set(y,{resolve:F,reject:N}),n.stdin?.write(`${JSON.stringify(C)}
4
+ `)})};fetch("http://127.0.0.1:7243/ingest/9e452bb4-cc67-4519-89fa-8fb51d810231",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({location:"mcp.ts:callStdioMcp:beforeInit",message:"sending initialize request",data:{elapsed:Date.now()-i},timestamp:Date.now(),sessionId:"debug-session",hypothesisId:"B,C"})}).catch(()=>{});const T=await E("initialize",{protocolVersion:"2024-11-05",clientInfo:{name:"tg-agent",version:process.env.npm_package_version??"dev"},capabilities:{}});if(fetch("http://127.0.0.1:7243/ingest/9e452bb4-cc67-4519-89fa-8fb51d810231",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({location:"mcp.ts:callStdioMcp:afterInit",message:"initialize response received",data:{elapsed:Date.now()-i,hasError:!!T.error},timestamp:Date.now(),sessionId:"debug-session",hypothesisId:"B,C"})}).catch(()=>{}),T.error&&typeof T.error=="object"){const f=T.error.message??"MCP initialize failed.";throw s(),new Error(String(f))}const D={jsonrpc:"2.0",method:"notifications/initialized"};n.stdin?.write(`${JSON.stringify(D)}
5
+ `),fetch("http://127.0.0.1:7243/ingest/9e452bb4-cc67-4519-89fa-8fb51d810231",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({location:"mcp.ts:callStdioMcp:beforeMethod",message:"sending method request",data:{method:e,elapsed:Date.now()-i},timestamp:Date.now(),sessionId:"debug-session",hypothesisId:"C"})}).catch(()=>{});const S=await E(e,r);a.removeEventListener("abort",g),p.close(),n.stdin?.end(),s();const $=new Promise(f=>{n.once("exit",()=>f())});if(await Promise.race([$,new Promise(f=>setTimeout(f,500))]),S.error&&typeof S.error=="object"){const f=S.error.message??"MCP request failed.";throw new Error(String(f))}const M=JSON.stringify(S,null,2),L=M.length>o?M.slice(0,o):M;return M.length>o&&(u=!0),{ok:!0,output:L,durationMs:Date.now()-i,bytes:Math.min(M.length,o),truncated:u}}async function ot(t,e,r){const o=t.body?.getReader?.();if(!o){const d=await t.text(),p=Math.min(d.length,e);return{text:d.slice(0,e),bytes:p,truncated:d.length>e}}const a=new TextDecoder("utf-8"),i=[];let n=0,s=!1;for(;;){if(r.aborted){try{await o.cancel()}catch{}throw new Error("MCP request aborted.")}const{done:d,value:p}=await o.read();if(d)break;if(!p)continue;const g=n+p.byteLength;if(g>e){const b=Math.max(0,e-n);b>0&&(i.push(p.slice(0,b)),n+=b),s=!0;try{await o.cancel()}catch{}break}i.push(p),n=g}const c=new Uint8Array(n);let u=0;for(const d of i)c.set(d,u),u+=d.byteLength;return{text:a.decode(c),bytes:n,truncated:s}}function O(t){const e=[];let r;try{r=U.parse(t)}catch{return e}const o=r.mcp_servers;if(!o||typeof o!="object")return e;for(const[a,i]of Object.entries(o)){if(!i||typeof i!="object")continue;const n=i,s=typeof n.type=="string"?n.type.toLowerCase():void 0,c=typeof n.url=="string"?n.url:void 0,u=typeof n.command=="string"?n.command:void 0,l=s==="http"||s==="stdio"?s:c?"http":u?"stdio":void 0;if(!l||l==="http"&&!c||l==="stdio"&&!u)continue;let d;Array.isArray(n.args)&&(d=n.args.filter(g=>typeof g=="string").map(g=>g.trim()));const p=typeof n.auth=="string"?n.auth:typeof n.authorization=="string"?n.authorization:typeof n.token=="string"?n.token:void 0;e.push({name:a,type:l,url:c,command:u,args:d,auth:p})}return e}function z(t){return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function gt(t){const e=t.trim();return e?e.startsWith('"')&&e.endsWith('"')?e.slice(1,-1).replace(/\\\\/g,"\\").replace(/\\"/g,'"'):e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e:""}function yt(t){const e=t.trim();if(!e.startsWith("[")||!e.endsWith("]"))return[];const r=e.slice(1,-1).trim();if(!r)return[];const o=[];let a="",i=!1,n="";for(let c=0;c<r.length;c+=1){const u=r[c];if(!i&&(u==='"'||u==="'")){i=!0,n=u;continue}if(i&&u===n){i=!1;continue}if(!i&&u===","){const l=a.trim();l&&o.push(z(l)),a="";continue}a+=u}const s=a.trim();return s&&o.push(z(s)),o}function wt(t){let e=!1,r="";for(let o=0;o<t.length;o+=1){const a=t[o];if(!e&&(a==='"'||a==="'")){e=!0,r=a;continue}if(e&&a===r){e=!1;continue}if(!e&&(a==="#"||a===";"))return t.slice(0,o)}return t}export{P as callMcpServer,ht as formatMcpTarget,dt as freezeMcpCatalog,ft as getMcpCatalog,I as getMcpConfigPath,Y as loadMcpServers,lt as loadMcpServersSync,mt as probeMcpServer,pt as refreshMcpCatalog};
@@ -1,7 +1,9 @@
1
- import{createAgentSession as P,discoverAuthStorage as j,discoverModels as B,SessionManager as N,SettingsManager as L}from"@mariozechner/pi-coding-agent";import{config as d}from"./config.js";import{readCodexOAuth as F}from"./codexAuth.js";import{createCustomTools as z}from"./customTools.js";import{getMcpCatalog as G}from"./mcp.js";import{ensureDir as k}from"./utils.js";import{sessionFilePath as q}from"./sessionStore.js";const K={"codex-mini-latest":"gpt-5.1-codex-mini","codex-max-latest":"gpt-5.1-codex-max","codex-latest":"gpt-5.2-codex"};function C(e){const t=e.trim().toLowerCase();return t==="codex"?"openai-codex":t}function V(e,t){const n=e.trim();if(!n)return{provider:t,modelId:""};if(n.includes("/")){const[i,r]=n.split("/",2);return{provider:i.trim()||t,modelId:r.trim()}}return{provider:t,modelId:n}}function X(e,t){return e!=="openai-codex"?t:K[t]??t}function H(e,t){const n=e.getAvailable().filter(r=>r.provider===t);return n.length>0?n[0]:e.getAll().filter(r=>r.provider===t)[0]}function J(e,t){const n=!!t?.provider?.trim(),i=!!t?.modelId?.trim(),r=C(t?.provider||d.modelProvider||"openai-codex"),s=n||i?"":d.modelRef||"",{provider:w,modelId:p}=V(s,r),u=t?.provider?r:C(w);let m=i?t?.modelId?.trim()||"":p;if(!m){const $=H(e,u);return $?{model:$,provider:u,modelId:$.id}:{model:void 0,provider:u,modelId:""}}const I=X(u,m);return{model:e.find(u,I),provider:u,modelId:I}}function Q(e){const t=F();return t?(e.setRuntimeApiKey("openai-codex",t.accessToken),{source:t.source,expiresAt:t.expiresAt}):null}function U(e){if(e?.role!=="assistant")return"";const n=e.content;if(typeof n=="string")return n.trim();if(!Array.isArray(n))return"";const i=n.filter(s=>typeof s=="object"&&s&&s.type==="text").map(s=>s.text??"").map(s=>s.trim()).filter(Boolean);if(i.length>0)return i.join(`
1
+ import{createAgentSession as z,discoverAuthStorage as N,discoverModels as q,SessionManager as K,SettingsManager as X}from"@mariozechner/pi-coding-agent";import b from"node:fs/promises";import v from"node:path";import{config as c}from"./config.js";import{readCodexOAuth as G}from"./codexAuth.js";import{createCustomTools as H}from"./customTools.js";import{getMcpCatalog as J}from"./mcp.js";import{ensureDir as T}from"./utils.js";import{sessionFilePath as Q}from"./sessionStore.js";const U={"codex-mini-latest":"gpt-5.1-codex-mini","codex-max-latest":"gpt-5.1-codex-max","codex-latest":"gpt-5.2-codex"};function P(e){const o=e.trim().toLowerCase();return o==="codex"?"openai-codex":o}function V(e,o){const s=e.trim();if(!s)return{provider:o,modelId:""};if(s.includes("/")){const[a,i]=s.split("/",2);return{provider:a.trim()||o,modelId:i.trim()}}return{provider:o,modelId:s}}function W(e,o){return e!=="openai-codex"?o:U[o]??o}function Y(e,o){const s=e.getAvailable().filter(i=>i.provider===o);return s.length>0?s[0]:e.getAll().filter(i=>i.provider===o)[0]}function Z(e,o){const s=!!o?.provider?.trim(),a=!!o?.modelId?.trim(),i=P(o?.provider||c.modelProvider||"openai-codex"),r=s||a?"":c.modelRef||"",{provider:y,modelId:M}=V(r,i),f=o?.provider?i:P(y);let m=a?o?.modelId?.trim()||"":M;if(!m){const l=Y(e,f);return l?{model:l,provider:f,modelId:l.id}:{model:void 0,provider:f,modelId:""}}const I=W(f,m);return{model:e.find(f,I),provider:f,modelId:I}}function ee(e){const o=G();return o?(e.setRuntimeApiKey("openai-codex",o.accessToken),{source:o.source,expiresAt:o.expiresAt}):null}function te(e){if(e?.role!=="assistant")return"";const s=e.content;if(typeof s=="string")return s.trim();if(!Array.isArray(s))return"";const a=s.filter(r=>typeof r=="object"&&r&&r.type==="text").map(r=>r.text??"").map(r=>r.trim()).filter(Boolean);if(a.length>0)return a.join(`
2
2
 
3
- `);const r=n.filter(s=>typeof s=="object"&&s&&s.type==="thinking").map(s=>s.thinking??"").map(s=>s.trim()).filter(Boolean);return r.length>0?r.join(`
3
+ `);const i=s.filter(r=>typeof r=="object"&&r&&r.type==="thinking").map(r=>r.thinking??"").map(r=>r.trim()).filter(Boolean);return i.length>0?i.join(`
4
4
 
5
- `):""}function W(e){if(e.length===0)return"messages=0";const t=e.slice(-6).map(n=>{const i=n?.role??"unknown",r=n.content,s=n?.stopReason,w=n?.errorMessage;if(typeof r=="string")return`${i}(text:${r.length}${s?`,stop=${s}`:""})`;if(Array.isArray(r)){const u=[`blocks:${r.map(m=>typeof m=="object"&&m&&"type"in m?String(m.type||"unknown"):typeof m).join(",")||"none"}`,s?`stop=${s}`:null,w?"error=1":null].filter(Boolean).join(",");return`${i}(${u})`}if(r==null){const p=["empty",s?`stop=${s}`:null,w?"error=1":null].filter(Boolean).join(",");return`${i}(${p})`}return`${i}(${typeof r}${s?`,stop=${s}`:""})`});return`messages=${e.length} last=[${t.join(" ")}]`}function Y(e,t){const n=t?.getLastAssistantText?.();if(n?.trim())return n.trim();for(let i=e.length-1;i>=0;i-=1){const r=U(e[i]);if(r)return r}return""}function Z(e){for(let t=e.length-1;t>=0;t-=1)if(e[t]?.role==="assistant")return e[t]}async function ae(e){await k(d.sessionDir),await k(d.agentDir),await k(d.workspaceDir);const t=q(e.userId,e.sessionId),n=j(d.agentDir),i=Q(n);i&&console.log(`[tg-agent] codex oauth source=${i.source} expiresAt=${new Date(i.expiresAt).toISOString()}`);const r=B(n,d.agentDir),{model:s,provider:w,modelId:p}=J(r,{provider:e.modelProvider,modelId:e.modelId});p&&!s&&console.warn(`[tg-agent] model not found: ${w}/${p}`);const u=N.open(t),m=L.create(d.workspaceDir,d.agentDir);let I="";try{I=await G(d.agentDir,{timeoutMs:4e3,maxBytes:d.fetchMaxBytes,maxChars:3e3})}catch(o){const a=o instanceof Error?o.message:String(o);console.warn(`[tg-agent] mcp catalog error: ${a}`)}const{session:l,modelFallbackMessage:$}=await P({cwd:d.workspaceDir,agentDir:d.agentDir,authStorage:n,modelRegistry:r,model:s??void 0,sessionManager:u,settingsManager:m,customTools:z({telegram:e.telegram}),systemPrompt:o=>{const a=e.systemPrompt?.trim(),c=[o];return a&&c.push(a),I&&c.push(I),c.join(`
5
+ `):""}function oe(e){if(e.length===0)return"messages=0";const o=e.slice(-6).map(s=>{const a=s?.role??"unknown",i=s.content,r=s?.stopReason,y=s?.errorMessage;if(typeof i=="string")return`${a}(text:${i.length}${r?`,stop=${r}`:""})`;if(Array.isArray(i)){const f=[`blocks:${i.map(m=>typeof m=="object"&&m&&"type"in m?String(m.type||"unknown"):typeof m).join(",")||"none"}`,r?`stop=${r}`:null,y?"error=1":null].filter(Boolean).join(",");return`${a}(${f})`}if(i==null){const M=["empty",r?`stop=${r}`:null,y?"error=1":null].filter(Boolean).join(",");return`${a}(${M})`}return`${a}(${typeof i}${r?`,stop=${r}`:""})`});return`messages=${e.length} last=[${o.join(" ")}]`}function ne(e){const o=e.trim();return o.toLowerCase().includes("usage limit")?`${o}
6
6
 
7
- `)}});$&&console.warn(`[tg-agent] modelFallback=${$}`);let b=!1;const R=()=>{b=!0,l.abort()};e.onAbortReady?.(R);const M=process.env.LOG_AGENT_EVENTS!=="0",v=process.env.LOG_AGENT_STREAM==="1",S=new Map,O=l.subscribe(o=>{switch(o.type){case"agent_start":M&&console.log(`[tg-agent] agent start session=${e.sessionId}`),f=Date.now(),e.onStatus?.({type:"agent_start"});break;case"agent_end":M&&console.log(`[tg-agent] agent end session=${e.sessionId}`),e.onStatus?.({type:"agent_end"});break;case"turn_start":M&&console.log(`[tg-agent] turn start session=${e.sessionId}`),f=Date.now(),e.onStatus?.({type:"turn_start"});break;case"turn_end":M&&console.log(`[tg-agent] turn end session=${e.sessionId} toolResults=${o.toolResults.length}`),f=Date.now(),e.onStatus?.({type:"turn_end",toolResults:o.toolResults.length});break;case"message_start":{if(M){const a=o.message.role??"unknown";console.log(`[tg-agent] message start session=${e.sessionId} role=${a}`)}f=Date.now();{const a=o.message.role??"unknown";e.onStatus?.({type:"message_start",role:a})}break}case"message_end":{if(M){const a=o.message.role??"unknown";console.log(`[tg-agent] message end session=${e.sessionId} role=${a}`)}f=Date.now();{const a=o.message.role??"unknown";e.onStatus?.({type:"message_end",role:a})}break}case"message_update":{if(!v)break;if(o.assistantMessageEvent.type==="text_delta"){const c=(o.assistantMessageEvent.delta??"").length;c>0&&console.log(`[tg-agent] stream delta session=${e.sessionId} chars=${c}`),f=Date.now()}o.assistantMessageEvent.type==="thinking_delta"&&(f=Date.now());break}case"tool_execution_start":{S.set(o.toolCallId,Date.now()),console.log(`[tg-agent] tool start name=${o.toolName} id=${o.toolCallId}`),A+=1,f=Date.now(),e.onStatus?.({type:"tool_start",name:o.toolName,id:o.toolCallId,args:o.args});break}case"tool_execution_end":{const a=S.get(o.toolCallId),c=a?Date.now()-a:0;console.log(`[tg-agent] tool end name=${o.toolName} id=${o.toolCallId} ok=${!o.isError} durationMs=${c}`),S.delete(o.toolCallId),A=Math.max(0,A-1),f=Date.now(),e.onStatus?.({type:"tool_end",name:o.toolName,id:o.toolCallId,ok:!o.isError,durationMs:c});break}default:break}});let h=null,_=null,y=!1,E=0,T=!1,f=Date.now(),A=0;try{const o=Date.now();h=setInterval(()=>{const g=Date.now()-o;console.log(`[tg-agent] prompt running session=${e.sessionId} elapsedMs=${g} streaming=${l.isStreaming}`),e.onStatus?.({type:"heartbeat",elapsedMs:g,streaming:l.isStreaming})},15e3),_=setInterval(()=>{if(y||A>0)return;const g=Date.now()-f,x=l.isStreaming?d.modelTimeoutStreamingMs:d.modelTimeoutMs,D=Math.max(1e3,x);g>=D&&(y=!0,E=D,T=l.isStreaming,console.warn(`[tg-agent] model timeout session=${e.sessionId} elapsedMs=${g}`),l.abort())},2e3);try{if(await l.prompt(e.prompt,e.images&&e.images.length>0?{images:e.images}:void 0),y)throw new Error(`Model request timed out after ${d.modelTimeoutMs}ms`)}catch(g){if(b)throw new Error("Cancelled by user.");if(y){const x=T?" (streaming)":"",D=E||d.modelTimeoutMs;throw new Error(`Model request timed out after ${D}ms${x}`)}throw g}h&&clearInterval(h);const a=Z(l.messages);if(a){const g=a?.stopReason,x=a?.errorMessage;if(g==="error")throw console.warn(`[tg-agent] model error session=${e.sessionId} error=${x??"unknown"}`),new Error(x??"Model error without details.")}const c=Y(l.messages,l);if(!c){const g=W(l.messages);throw console.warn(`[tg-agent] empty response session=${e.sessionId} ${g}`),new Error("No assistant response.")}return{text:c,sessionFile:t,sessionId:l.sessionId,modelProvider:l.model?.provider??w,modelId:l.model?.id??p,modelFallbackMessage:$}}finally{h&&clearInterval(h),_&&clearInterval(_),O(),u.flushPendingToolResults?.(),l.dispose()}}export{ae as runPiAgentPrompt};
7
+ Suggestion: wait for the limit to reset or switch to another provider/model.`:o}function se(e,o){const s=o?.getLastAssistantText?.();if(s?.trim())return s.trim();for(let a=e.length-1;a>=0;a-=1){const i=te(e[a]);if(i)return i}return""}function re(e){for(let o=e.length-1;o>=0;o-=1)if(e[o]?.role==="assistant")return e[o]}async function pe(e){const o=e.workspaceDir||c.workspaceDir;await T(c.sessionDir),await T(c.agentDir),await T(o);const s=Q(e.chatId,e.sessionId),a=N(c.agentDir),i=ee(a);i&&console.log(`[tg-agent] codex oauth source=${i.source} expiresAt=${new Date(i.expiresAt).toISOString()}`);const r=q(a,c.agentDir),{model:y,provider:M,modelId:f}=Z(r,{provider:e.modelProvider,modelId:e.modelId});f&&!y&&console.warn(`[tg-agent] model not found: ${M}/${f}`);const m=K.open(s),I=X.create(o,c.agentDir);try{const t=v.join(c.agentDir,"skills"),n=v.join(o,".pi","skills"),d=await b.access(t).then(()=>!0).catch(()=>!1),g=await b.access(n).then(()=>!0).catch(()=>!1);if(d){const h=(await b.readdir(t,{withFileTypes:!0})).filter($=>$.isDirectory()).map($=>$.name);console.log(`[tg-agent] skills directory found at ${t} with ${h.length} skill(s): ${h.join(", ")}`)}else console.log(`[tg-agent] skills directory not found at ${t}`);if(g){const h=(await b.readdir(n,{withFileTypes:!0})).filter($=>$.isDirectory()).map($=>$.name);console.log(`[tg-agent] workspace skills directory found at ${n} with ${h.length} skill(s): ${h.join(", ")}`)}const w=I.getSettings?.();if(w){const h=w.skills?.enabled!==!1;console.log(`[tg-agent] skills enabled in settings: ${h}`)}}catch(t){const n=t instanceof Error?t.message:String(t);console.warn(`[tg-agent] skills check failed: ${n}`)}let D="";try{D=await J(c.agentDir,{timeoutMs:4e3,maxBytes:c.fetchMaxBytes,maxChars:3e3})}catch(t){const n=t instanceof Error?t.message:String(t);console.warn(`[tg-agent] mcp catalog error: ${n}`)}const{session:l,modelFallbackMessage:_}=await z({cwd:o,agentDir:c.agentDir,authStorage:a,modelRegistry:r,model:y??void 0,sessionManager:m,settingsManager:I,customTools:H({telegram:e.telegram}),systemPrompt:t=>{const n=e.systemPrompt?.trim(),d=[t];return n&&d.push(n),D&&d.push(D),d.join(`
8
+
9
+ `)}});try{const t=l.messages,n=t.some(u=>{const h=u.role,$=u.content;return h==="system"&&typeof $=="string"?$.toLowerCase().includes("compaction")||$.toLowerCase().includes("compact"):u.type==="compaction"||u.compaction!==void 0});console.log(`[tg-agent] session manager type: ${typeof m}`),console.log(`[tg-agent] session messages count: ${t.length}`),console.log(`[tg-agent] compacting support detected: ${n||"unknown (will be detected during runtime)"}`);const g=typeof m.compact=="function",w=typeof m.appendCompaction=="function";console.log(`[tg-agent] SessionManager.compact method: ${g}`),console.log(`[tg-agent] SessionManager.appendCompaction method: ${w}`)}catch(t){const n=t instanceof Error?t.message:String(t);console.warn(`[tg-agent] compacting check failed: ${n}`)}_&&console.warn(`[tg-agent] modelFallback=${_}`);let R=!1;const B=()=>{R=!0,l.abort()};e.onAbortReady?.(B);const k=c.logAgentEvents,F=c.logAgentStream,C=new Map,L=l.subscribe(t=>{switch(t.type){case"agent_start":k&&console.log(`[tg-agent] agent start session=${e.sessionId}`),p=Date.now(),e.onStatus?.({type:"agent_start"});break;case"agent_end":k&&console.log(`[tg-agent] agent end session=${e.sessionId}`),e.onStatus?.({type:"agent_end"});break;case"turn_start":k&&console.log(`[tg-agent] turn start session=${e.sessionId}`),p=Date.now(),e.onStatus?.({type:"turn_start"});break;case"turn_end":k&&console.log(`[tg-agent] turn end session=${e.sessionId} toolResults=${t.toolResults.length}`),p=Date.now(),e.onStatus?.({type:"turn_end",toolResults:t.toolResults.length});break;case"message_start":{if(k){const n=t.message.role??"unknown";console.log(`[tg-agent] message start session=${e.sessionId} role=${n}`)}p=Date.now();{const n=t.message.role??"unknown";e.onStatus?.({type:"message_start",role:n})}break}case"message_end":{if(k){const n=t.message.role??"unknown";console.log(`[tg-agent] message end session=${e.sessionId} role=${n}`)}p=Date.now();{const n=t.message.role??"unknown";e.onStatus?.({type:"message_end",role:n})}break}case"message_update":{if(!F)break;if(t.assistantMessageEvent.type==="text_delta"){const d=(t.assistantMessageEvent.delta??"").length;d>0&&console.log(`[tg-agent] stream delta session=${e.sessionId} chars=${d}`),p=Date.now()}t.assistantMessageEvent.type==="thinking_delta"&&(p=Date.now());break}case"tool_execution_start":{C.set(t.toolCallId,Date.now()),console.log(`[tg-agent] tool start name=${t.toolName} id=${t.toolCallId}`),A+=1,p=Date.now(),e.onStatus?.({type:"tool_start",name:t.toolName,id:t.toolCallId,args:t.args});break}case"tool_execution_end":{const n=C.get(t.toolCallId),d=n?Date.now()-n:0;console.log(`[tg-agent] tool end name=${t.toolName} id=${t.toolCallId} ok=${!t.isError} durationMs=${d}`),C.delete(t.toolCallId),A=Math.max(0,A-1),p=Date.now(),e.onStatus?.({type:"tool_end",name:t.toolName,id:t.toolCallId,ok:!t.isError,durationMs:d});break}case"auto_compaction_start":{const n=t.reason??"unknown";console.log(`[tg-agent] auto compaction started session=${e.sessionId} reason=${n}`),p=Date.now();break}case"auto_compaction_end":{const n=t.willRetry??!1;console.log(`[tg-agent] auto compaction ended session=${e.sessionId} willRetry=${n}`),p=Date.now();break}default:break}});let x=null,E=null,S=!1,j=0,O=!1,p=Date.now(),A=0;try{const t=Date.now();x=setInterval(()=>{const g=Date.now()-t;console.log(`[tg-agent] prompt running session=${e.sessionId} elapsedMs=${g} streaming=${l.isStreaming}`),e.onStatus?.({type:"heartbeat",elapsedMs:g,streaming:l.isStreaming})},15e3),E=setInterval(()=>{if(S||A>0)return;const g=Date.now()-p,w=l.isStreaming?c.modelTimeoutStreamingMs:c.modelTimeoutMs,u=Math.max(1e3,w);g>=u&&(S=!0,j=u,O=l.isStreaming,console.warn(`[tg-agent] model timeout session=${e.sessionId} elapsedMs=${g}`),l.abort())},2e3);try{if(await l.prompt(e.prompt,e.images&&e.images.length>0?{images:e.images}:void 0),S)throw new Error(`Model request timed out after ${c.modelTimeoutMs}ms`)}catch(g){if(R)throw new Error("Cancelled by user.");if(S){const w=O?" (streaming)":"",u=j||c.modelTimeoutMs;throw new Error(`Model request timed out after ${u}ms${w}`)}throw g}x&&clearInterval(x);const n=re(l.messages);if(n){const g=n?.stopReason,w=n?.errorMessage;if(g==="error"){const u=w?ne(w):"Model error without details.";throw console.warn(`[tg-agent] model error session=${e.sessionId} error=${u}`),new Error(u)}}const d=se(l.messages,l);if(!d){const g=oe(l.messages);throw console.warn(`[tg-agent] empty response session=${e.sessionId} ${g}`),new Error("No assistant response.")}return{text:d,sessionFile:s,sessionId:l.sessionId,modelProvider:l.model?.provider??M,modelId:l.model?.id??f,modelFallbackMessage:_}}finally{x&&clearInterval(x),E&&clearInterval(E),L(),m.flushPendingToolResults?.(),l.dispose()}}export{pe as runPiAgentPrompt};
@@ -1 +1 @@
1
- import u from"node:fs/promises";import f from"node:path";import{config as r}from"./config.js";import{atomicWriteJson as h,ensureDir as l,nowMs as a,shortId as m}from"./utils.js";const c=new Map;function p(e){return f.join(r.sessionDir,`${e}.json`)}function d(e){return e.replace(/[^a-zA-Z0-9_-]/g,"_")}function g(e,s){const t=d(e),n=d(s);return f.join(r.sessionDir,`${t}-${n}.jsonl`)}async function j(e,s){const t=g(e,s);try{await u.unlink(t)}catch(n){if(n.code==="ENOENT")return;throw n}}function S(e,s){const t=s?.sessions??{},n=s?.activeSessionId??null,o={userId:e,activeSessionId:n,sessions:t};return(!n||!t[n])&&(o.activeSessionId=null),o}async function E(e){await l(r.sessionDir);const s=c.get(e);if(s)return s;const t=p(e);try{const n=await u.readFile(t,"utf8"),o=JSON.parse(n),i=S(e,o);return c.set(e,i),i}catch(n){if(n.code==="ENOENT"){const o=S(e,{});return c.set(e,o),o}throw n}}async function y(e){await l(r.sessionDir);const s=p(e.userId);await h(s,e),c.set(e.userId,e)}function z(e){return Object.values(e.sessions).sort((s,t)=>t.updatedAt-s.updatedAt)}function D(e){return[]}function N(e,s){if(Object.keys(e.sessions).length>=r.maxSessions)throw new Error("Max sessions reached");const n=m(),o=a(),i={id:n,title:s&&s.length>0?s:`session-${n}`,messages:[],createdAt:o,updatedAt:o};return e.sessions[n]=i,e.activeSessionId=n,i}function O(e,s){return e.sessions[s]?(e.activeSessionId=s,!0):!1}function P(e){return e.activeSessionId?e.sessions[e.activeSessionId]??null:null}function F(e,s){return e.sessions[s]?(delete e.sessions[s],e.activeSessionId===s&&(e.activeSessionId=null),!0):!1}function I(e){e.messages=[],e.updatedAt=a()}function $(e,s,t){e.messages.push(s),e.messages.length>t&&(e.messages=e.messages.slice(-t)),e.updatedAt=a()}export{$ as appendMessage,F as closeSession,N as createSession,j as deleteSessionFile,P as getActiveSession,z as listSessions,E as loadUserState,D as pruneExpiredSessions,I as resetSession,y as saveUserState,g as sessionFilePath,O as setActiveSession};
1
+ import u from"node:fs/promises";import f from"node:path";import{config as i}from"./config.js";import{atomicWriteJson as h,ensureDir as p,nowMs as a,shortId as m}from"./utils.js";const c=new Map;function l(e){return f.join(i.sessionDir,`${e}.json`)}function d(e){return e.replace(/[^a-zA-Z0-9_-]/g,"_")}function x(e,s){const n=d(e),t=d(s);return f.join(i.sessionDir,`${n}-${t}.jsonl`)}async function j(e,s){const n=x(e,s);try{await u.unlink(n)}catch(t){if(t.code==="ENOENT")return;throw t}}function S(e,s){const n=s?.sessions??{},t=s?.activeSessionId??null,o=s?.workspaceDir,r={chatId:e,workspaceDir:o,activeSessionId:t,sessions:n};return(!t||!n[t])&&(r.activeSessionId=null),r}async function g(e){await p(i.sessionDir);const s=c.get(e);if(s)return s;const n=l(e);try{const t=await u.readFile(n,"utf8"),o=JSON.parse(t),r=S(e,o);return c.set(e,r),r}catch(t){if(t.code==="ENOENT"){const o=S(e,{});return c.set(e,o),o}throw t}}async function w(e){await p(i.sessionDir);const s=l(e.chatId);await h(s,e),c.set(e.chatId,e)}function E(e){return Object.values(e.sessions).sort((s,n)=>n.updatedAt-s.updatedAt)}function y(e){return[]}function z(e,s){if(Object.keys(e.sessions).length>=i.maxSessions)throw new Error("Max sessions reached");const t=m(),o=a(),r={id:t,title:s&&s.length>0?s:`session-${t}`,messages:[],createdAt:o,updatedAt:o};return e.sessions[t]=r,e.activeSessionId=t,r}function N(e,s){return e.sessions[s]?(e.activeSessionId=s,!0):!1}function O(e){return e.activeSessionId?e.sessions[e.activeSessionId]??null:null}function P(e,s){return e.sessions[s]?(delete e.sessions[s],e.activeSessionId===s&&(e.activeSessionId=null),!0):!1}function F(e){e.messages=[],e.updatedAt=a()}function I(e,s,n){e.messages.push(s),e.messages.length>n&&(e.messages=e.messages.slice(-n)),e.updatedAt=a()}function $(e,s){e.workspaceDir=s}function C(e){return e.workspaceDir}const M=g,W=w;export{I as appendMessage,P as closeSession,z as createSession,j as deleteSessionFile,O as getActiveSession,C as getWorkspaceDir,E as listSessions,g as loadChatState,M as loadUserState,y as pruneExpiredSessions,F as resetSession,w as saveChatState,W as saveUserState,x as sessionFilePath,N as setActiveSession,$ as setWorkspaceDir};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tg-agent",
3
- "version": "1.1.3",
3
+ "version": "1.1.8",
4
4
  "description": "Telegram Codex agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,22 +8,21 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
- ".env.example",
12
11
  "README.md",
13
12
  "LICENSE"
14
13
  ],
15
14
  "scripts": {
16
- "dev": "tsx src/index.ts",
15
+ "dev": "tsx src/cli.ts",
17
16
  "build": "tsc -p tsconfig.json && node scripts/minify.mjs && node scripts/add-shebang.mjs",
18
17
  "minify": "node scripts/minify.mjs",
19
18
  "prepublishOnly": "npm run build",
20
- "start": "node dist/index.js"
19
+ "start": "node dist/cli.js"
21
20
  },
22
21
  "dependencies": {
22
+ "@iarna/toml": "^2.2.5",
23
23
  "@mariozechner/pi-ai": "^0.46.0",
24
24
  "@mariozechner/pi-coding-agent": "^0.46.0",
25
25
  "@sinclair/typebox": "^0.32.35",
26
- "dotenv": "^16.4.5",
27
26
  "https-proxy-agent": "^7.0.5",
28
27
  "node-telegram-bot-api": "^0.64.0",
29
28
  "openai": "^4.61.0",
package/.env.example DELETED
@@ -1,50 +0,0 @@
1
- # Required
2
- TELEGRAM_BOT_TOKEN=
3
-
4
- # Optional allowlist (comma-separated user IDs)
5
- TELEGRAM_ALLOWED_USER_IDS=
6
-
7
- # Codex auth location (optional)
8
- CODEX_HOME=~/.codex
9
-
10
- # Runtime dirs
11
- AGENT_DIR=~/.tg-agent
12
- WORKSPACE_DIR=~/
13
- SESSION_DIR=~/.codex/tg-sessions
14
-
15
- # Model selection
16
- LLM_PROVIDER=openai-codex
17
- LLM_MODEL=gpt-5.2
18
- OPENAI_API_KEY=
19
-
20
- # Limits
21
- MAX_SESSIONS=3
22
- MAX_CONCURRENT=3
23
- SESSION_TTL_MIN=60
24
- MAX_HISTORY_MESSAGES=40
25
-
26
- # Timeouts
27
- MODEL_TIMEOUT_MS=60000
28
- MODEL_TIMEOUT_STREAM_MS=300000
29
-
30
- # Fetch tool
31
- FETCH_MAX_BYTES=200000
32
- FETCH_TIMEOUT_MS=60000
33
- FETCH_PROXY_URL=
34
-
35
- # Proxy for model + global traffic (or use standard HTTP(S)_PROXY / ALL_PROXY)
36
- PROXY_URL=
37
-
38
- # MCP
39
-
40
- # TLS (optional)
41
- NODE_EXTRA_CA_CERTS=
42
- NODE_TLS_REJECT_UNAUTHORIZED=
43
-
44
- # Logging
45
- LOG_AGENT_EVENTS=1
46
- LOG_AGENT_STREAM=0
47
-
48
- # Output / prompt
49
- SYSTEM_PROMPT=You are Codex running locally. Be concise and practical.
50
- TELEGRAM_PARSE_MODE=