tg-agent 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,138 +1 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { config } from "./config.js";
4
- import { atomicWriteJson, ensureDir, nowMs, shortId } from "./utils.js";
5
- const cache = new Map();
6
- function userFilePath(userId) {
7
- return path.join(config.sessionDir, `${userId}.json`);
8
- }
9
- function sanitizeSegment(value) {
10
- return value.replace(/[^a-zA-Z0-9_-]/g, "_");
11
- }
12
- export function sessionFilePath(userId, sessionId) {
13
- const safeUser = sanitizeSegment(userId);
14
- const safeSession = sanitizeSegment(sessionId);
15
- return path.join(config.sessionDir, `${safeUser}-${safeSession}.jsonl`);
16
- }
17
- export async function deleteSessionFile(userId, sessionId) {
18
- const filePath = sessionFilePath(userId, sessionId);
19
- try {
20
- await fs.unlink(filePath);
21
- }
22
- catch (error) {
23
- if (error.code === "ENOENT") {
24
- return;
25
- }
26
- throw error;
27
- }
28
- }
29
- function normalizeState(userId, raw) {
30
- const sessions = raw?.sessions ?? {};
31
- const activeSessionId = raw?.activeSessionId ?? null;
32
- const normalized = {
33
- userId,
34
- activeSessionId,
35
- sessions,
36
- };
37
- if (!activeSessionId || !sessions[activeSessionId]) {
38
- normalized.activeSessionId = null;
39
- }
40
- return normalized;
41
- }
42
- export async function loadUserState(userId) {
43
- await ensureDir(config.sessionDir);
44
- const cached = cache.get(userId);
45
- if (cached) {
46
- return cached;
47
- }
48
- const filePath = userFilePath(userId);
49
- try {
50
- const raw = await fs.readFile(filePath, "utf8");
51
- const parsed = JSON.parse(raw);
52
- const normalized = normalizeState(userId, parsed);
53
- cache.set(userId, normalized);
54
- return normalized;
55
- }
56
- catch (error) {
57
- if (error.code === "ENOENT") {
58
- const state = normalizeState(userId, {});
59
- cache.set(userId, state);
60
- return state;
61
- }
62
- throw error;
63
- }
64
- }
65
- export async function saveUserState(state) {
66
- await ensureDir(config.sessionDir);
67
- const filePath = userFilePath(state.userId);
68
- await atomicWriteJson(filePath, state);
69
- cache.set(state.userId, state);
70
- }
71
- export function listSessions(state) {
72
- return Object.values(state.sessions).sort((a, b) => b.updatedAt - a.updatedAt);
73
- }
74
- export function pruneExpiredSessions(state, now = nowMs()) {
75
- const removed = [];
76
- for (const [id, session] of Object.entries(state.sessions)) {
77
- if (now - session.updatedAt > config.sessionTtlMs) {
78
- delete state.sessions[id];
79
- removed.push(id);
80
- }
81
- }
82
- if (state.activeSessionId && !state.sessions[state.activeSessionId]) {
83
- state.activeSessionId = null;
84
- }
85
- return removed;
86
- }
87
- export function createSession(state, title) {
88
- const count = Object.keys(state.sessions).length;
89
- if (count >= config.maxSessions) {
90
- throw new Error("Max sessions reached");
91
- }
92
- const id = shortId();
93
- const now = nowMs();
94
- const session = {
95
- id,
96
- title: title && title.length > 0 ? title : `session-${id}`,
97
- messages: [],
98
- createdAt: now,
99
- updatedAt: now,
100
- };
101
- state.sessions[id] = session;
102
- state.activeSessionId = id;
103
- return session;
104
- }
105
- export function setActiveSession(state, sessionId) {
106
- if (!state.sessions[sessionId]) {
107
- return false;
108
- }
109
- state.activeSessionId = sessionId;
110
- return true;
111
- }
112
- export function getActiveSession(state) {
113
- if (!state.activeSessionId) {
114
- return null;
115
- }
116
- return state.sessions[state.activeSessionId] ?? null;
117
- }
118
- export function closeSession(state, sessionId) {
119
- if (!state.sessions[sessionId]) {
120
- return false;
121
- }
122
- delete state.sessions[sessionId];
123
- if (state.activeSessionId === sessionId) {
124
- state.activeSessionId = null;
125
- }
126
- return true;
127
- }
128
- export function resetSession(session) {
129
- session.messages = [];
130
- session.updatedAt = nowMs();
131
- }
132
- export function appendMessage(session, message, maxHistoryMessages) {
133
- session.messages.push(message);
134
- if (session.messages.length > maxHistoryMessages) {
135
- session.messages = session.messages.slice(-maxHistoryMessages);
136
- }
137
- session.updatedAt = nowMs();
138
- }
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 l,nowMs as c,shortId as m}from"./utils.js";const a=new Map;function d(e){return f.join(i.sessionDir,`${e}.json`)}function p(e){return e.replace(/[^a-zA-Z0-9_-]/g,"_")}function v(e,s){const o=p(e),n=p(s);return f.join(i.sessionDir,`${o}-${n}.jsonl`)}async function j(e,s){const o=v(e,s);try{await u.unlink(o)}catch(n){if(n.code==="ENOENT")return;throw n}}function S(e,s){const o=s?.sessions??{},n=s?.activeSessionId??null,t={userId:e,activeSessionId:n,sessions:o};return(!n||!o[n])&&(t.activeSessionId=null),t}async function I(e){await l(i.sessionDir);const s=a.get(e);if(s)return s;const o=d(e);try{const n=await u.readFile(o,"utf8"),t=JSON.parse(n),r=S(e,t);return a.set(e,r),r}catch(n){if(n.code==="ENOENT"){const t=S(e,{});return a.set(e,t),t}throw n}}async function E(e){await l(i.sessionDir);const s=d(e.userId);await h(s,e),a.set(e.userId,e)}function O(e){return Object.values(e.sessions).sort((s,o)=>o.updatedAt-s.updatedAt)}function y(e,s=c()){const o=[];for(const[n,t]of Object.entries(e.sessions))s-t.updatedAt>i.sessionTtlMs&&(delete e.sessions[n],o.push(n));return e.activeSessionId&&!e.sessions[e.activeSessionId]&&(e.activeSessionId=null),o}function z(e,s){if(Object.keys(e.sessions).length>=i.maxSessions)throw new Error("Max sessions reached");const n=m(),t=c(),r={id:n,title:s&&s.length>0?s:`session-${n}`,messages:[],createdAt:t,updatedAt:t};return e.sessions[n]=r,e.activeSessionId=n,r}function D(e,s){return e.sessions[s]?(e.activeSessionId=s,!0):!1}function N(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=c()}function M(e,s,o){e.messages.push(s),e.messages.length>o&&(e.messages=e.messages.slice(-o)),e.updatedAt=c()}export{M as appendMessage,P as closeSession,z as createSession,j as deleteSessionFile,N as getActiveSession,O as listSessions,I as loadUserState,y as pruneExpiredSessions,F as resetSession,E as saveUserState,v as sessionFilePath,D as setActiveSession};
package/dist/types.js CHANGED
@@ -1 +0,0 @@
1
- export {};
package/dist/utils.js CHANGED
@@ -1,91 +1,2 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { randomUUID } from "node:crypto";
5
- export function nowMs() {
6
- return Date.now();
7
- }
8
- export function expandHome(inputPath) {
9
- if (!inputPath.startsWith("~")) {
10
- return inputPath;
11
- }
12
- return path.join(os.homedir(), inputPath.slice(1));
13
- }
14
- export async function ensureDir(dirPath) {
15
- await fs.mkdir(dirPath, { recursive: true });
16
- }
17
- export async function atomicWriteJson(filePath, data) {
18
- const dir = path.dirname(filePath);
19
- const tmpName = `${path.basename(filePath)}.${randomUUID()}.tmp`;
20
- const tmpPath = path.join(dir, tmpName);
21
- const json = JSON.stringify(data, null, 2);
22
- await fs.writeFile(tmpPath, json, "utf8");
23
- await fs.rename(tmpPath, filePath);
24
- }
25
- export function shortId() {
26
- return randomUUID().split("-")[0];
27
- }
28
- export function chunkText(text, maxLen) {
29
- if (text.length <= maxLen) {
30
- return [text];
31
- }
32
- const chunks = [];
33
- let remaining = text;
34
- while (remaining.length > maxLen) {
35
- let slice = remaining.slice(0, maxLen);
36
- const lastNewline = slice.lastIndexOf("\n");
37
- if (lastNewline > maxLen * 0.6) {
38
- slice = slice.slice(0, lastNewline);
39
- }
40
- chunks.push(slice);
41
- remaining = remaining.slice(slice.length);
42
- }
43
- if (remaining.length > 0) {
44
- chunks.push(remaining);
45
- }
46
- return chunks;
47
- }
48
- export function createQueueMap() {
49
- const queues = new Map();
50
- return async function enqueue(key, task) {
51
- const prev = queues.get(key) ?? Promise.resolve();
52
- const next = prev.then(task, task);
53
- queues.set(key, next);
54
- try {
55
- return await next;
56
- }
57
- finally {
58
- if (queues.get(key) === next) {
59
- queues.delete(key);
60
- }
61
- }
62
- };
63
- }
64
- export function createSemaphore(limit) {
65
- let active = 0;
66
- const queue = [];
67
- const next = () => {
68
- if (active >= limit) {
69
- return;
70
- }
71
- const resolve = queue.shift();
72
- if (!resolve) {
73
- return;
74
- }
75
- active += 1;
76
- resolve();
77
- };
78
- return async function withSemaphore(task) {
79
- await new Promise((resolve) => {
80
- queue.push(resolve);
81
- next();
82
- });
83
- try {
84
- return await task();
85
- }
86
- finally {
87
- active = Math.max(0, active - 1);
88
- next();
89
- }
90
- };
91
- }
1
+ import c from"node:fs/promises";import f from"node:os";import s from"node:path";import{randomUUID as u}from"node:crypto";function w(){return Date.now()}function d(e){return e.startsWith("~")?s.join(f.homedir(),e.slice(1)):e}async function x(e){await c.mkdir(e,{recursive:!0})}async function g(e,o){const n=s.dirname(e),t=`${s.basename(e)}.${u()}.tmp`,r=s.join(n,t),i=JSON.stringify(o,null,2);await c.writeFile(r,i,"utf8"),await c.rename(r,e)}function v(){return u().split("-")[0]}function y(e,o){if(e.length<=o)return[e];const n=[];let t=e;for(;t.length>o;){let r=t.slice(0,o);const i=r.lastIndexOf(`
2
+ `);i>o*.6&&(r=r.slice(0,i)),n.push(r),t=t.slice(r.length)}return t.length>0&&n.push(t),n}function M(){const e=new Map;return async function(n,t){const i=(e.get(n)??Promise.resolve()).then(t,t);e.set(n,i);try{return await i}finally{e.get(n)===i&&e.delete(n)}}}function j(e){let o=0;const n=[],t=()=>{if(o>=e)return;const r=n.shift();r&&(o+=1,r())};return async function(i){await new Promise(a=>{n.push(a),t()});try{return await i()}finally{o=Math.max(0,o-1),t()}}}export{g as atomicWriteJson,y as chunkText,M as createQueueMap,j as createSemaphore,x as ensureDir,d as expandHome,w as nowMs,v as shortId};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tg-agent",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "description": "Telegram Codex agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,8 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "dev": "tsx src/index.ts",
17
- "build": "tsc -p tsconfig.json && node scripts/add-shebang.mjs",
17
+ "build": "tsc -p tsconfig.json && node scripts/minify.mjs && node scripts/add-shebang.mjs",
18
+ "minify": "node scripts/minify.mjs",
18
19
  "prepublishOnly": "npm run build",
19
20
  "start": "node dist/index.js"
20
21
  },
@@ -30,6 +31,7 @@
30
31
  "undici": "^7.18.2"
31
32
  },
32
33
  "devDependencies": {
34
+ "esbuild": "^0.25.0",
33
35
  "@types/node": "^20.14.10",
34
36
  "@types/node-telegram-bot-api": "^0.64.7",
35
37
  "tsx": "^4.19.1",