@ironcode-ai/telegram 1.17.3 → 1.17.4
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 +4 -1
- package/package.json +1 -1
- package/src/index.ts +150 -22
package/README.md
CHANGED
|
@@ -74,16 +74,19 @@ The agent runs with the current directory as its working directory, so it can re
|
|
|
74
74
|
| `/sessions` | List recent sessions — tap to switch |
|
|
75
75
|
| `/new` | Start a new session |
|
|
76
76
|
| `/info` | Show current session details (title, ID, file changes) |
|
|
77
|
+
| `/init` | Analyze the project and create an `AGENTS.md` config file |
|
|
78
|
+
| `/diff` | Show all file changes made in the current session |
|
|
77
79
|
| `/start` | Show help |
|
|
78
80
|
|
|
79
81
|
## How It Works
|
|
80
82
|
|
|
81
83
|
```
|
|
82
84
|
You send a message
|
|
85
|
+
→ Bot shows 🤔 Thinking... while the agent starts up
|
|
83
86
|
→ Bot creates/resumes an ironcode session on your machine
|
|
84
87
|
→ Agent reads/writes files, runs bash, calls LLM
|
|
85
88
|
→ Each completed tool call is sent as a separate message
|
|
86
|
-
→ Text response is streamed live by editing
|
|
89
|
+
→ Text response is streamed live by editing the placeholder
|
|
87
90
|
→ Final response replaces the placeholder when done
|
|
88
91
|
```
|
|
89
92
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -107,8 +107,24 @@ function sessionLabel(s: Session, isCurrent: boolean) {
|
|
|
107
107
|
const bot = new Bot(cfg.token)
|
|
108
108
|
|
|
109
109
|
console.log("🚀 Starting ironcode server...")
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
let localServer: Awaited<ReturnType<typeof createIroncode>>
|
|
111
|
+
try {
|
|
112
|
+
localServer = await createIroncode({ port: 0 })
|
|
113
|
+
} catch (err: any) {
|
|
114
|
+
const msg = err?.message ?? String(err)
|
|
115
|
+
if (msg.includes("exited with code 0") || msg.includes("ENOENT") || msg.includes("Illegal instruction")) {
|
|
116
|
+
console.error("❌ Failed to start ironcode server.\n")
|
|
117
|
+
console.error(" Make sure the ironcode CLI is installed and authenticated:")
|
|
118
|
+
console.error(" 1. npm install -g ironcode-ai")
|
|
119
|
+
console.error(" 2. ironcode auth login")
|
|
120
|
+
console.error(" 3. ironcode serve ← test manually first\n")
|
|
121
|
+
} else {
|
|
122
|
+
console.error("❌ Failed to start ironcode server:", msg)
|
|
123
|
+
}
|
|
124
|
+
process.exit(1)
|
|
125
|
+
}
|
|
126
|
+
console.log("✅ Ironcode server ready at", localServer.server.url)
|
|
127
|
+
const client = localServer.client
|
|
112
128
|
|
|
113
129
|
type SessionState = {
|
|
114
130
|
sessionId: string
|
|
@@ -117,6 +133,7 @@ type SessionState = {
|
|
|
117
133
|
liveMessageId?: number
|
|
118
134
|
liveText: string
|
|
119
135
|
lastEditMs: number
|
|
136
|
+
currentTool?: string
|
|
120
137
|
}
|
|
121
138
|
|
|
122
139
|
const EDIT_INTERVAL_MS = 1200
|
|
@@ -136,7 +153,7 @@ async function editLive(state: SessionState, text: string) {
|
|
|
136
153
|
|
|
137
154
|
// Event loop
|
|
138
155
|
;(async () => {
|
|
139
|
-
const events = await
|
|
156
|
+
const events = await client.event.subscribe()
|
|
140
157
|
for await (const event of events.stream) {
|
|
141
158
|
const getState = (sessionID: string) =>
|
|
142
159
|
[...sessions.values()].find((s) => s.sessionId === sessionID)
|
|
@@ -153,13 +170,26 @@ async function editLive(state: SessionState, text: string) {
|
|
|
153
170
|
await editLive(state, state.liveText)
|
|
154
171
|
state.lastEditMs = now
|
|
155
172
|
}
|
|
156
|
-
} else if (part.type === "tool"
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
} else if (part.type === "tool") {
|
|
174
|
+
if (part.state?.status === "completed") {
|
|
175
|
+
state.currentTool = undefined
|
|
176
|
+
await bot.api
|
|
177
|
+
.sendMessage(state.chatId, `🔧 *${part.tool}* — ${part.state.title}`, {
|
|
178
|
+
parse_mode: "Markdown",
|
|
179
|
+
...(state.threadId ? { message_thread_id: state.threadId } : {}),
|
|
180
|
+
})
|
|
181
|
+
.catch(() => {})
|
|
182
|
+
} else if (state.currentTool !== part.tool) {
|
|
183
|
+
// Tool just started — show loading indicator if no text yet
|
|
184
|
+
state.currentTool = part.tool
|
|
185
|
+
if (!state.liveText.trim() && state.liveMessageId) {
|
|
186
|
+
const now = Date.now()
|
|
187
|
+
if (now - state.lastEditMs > 500) {
|
|
188
|
+
await editLive(state, `⏳ ${part.tool}...`)
|
|
189
|
+
state.lastEditMs = now
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
163
193
|
}
|
|
164
194
|
} else if (event.type === "message.updated") {
|
|
165
195
|
const info = event.properties.info as any
|
|
@@ -172,18 +202,31 @@ async function editLive(state: SessionState, text: string) {
|
|
|
172
202
|
await editLive(state, `❌ ${msg}`)
|
|
173
203
|
state.liveMessageId = undefined
|
|
174
204
|
state.liveText = ""
|
|
205
|
+
state.currentTool = undefined
|
|
175
206
|
continue
|
|
176
207
|
}
|
|
177
208
|
|
|
178
209
|
if (info.finish && info.finish !== "tool-calls" && info.finish !== "unknown") {
|
|
179
210
|
const finalText = state.liveText.trim()
|
|
211
|
+
const savedMessageId = state.liveMessageId
|
|
212
|
+
|
|
180
213
|
if (finalText) {
|
|
181
214
|
await editLive(state, finalText)
|
|
182
|
-
} else if (
|
|
183
|
-
|
|
215
|
+
} else if (savedMessageId) {
|
|
216
|
+
// No text output (only tools ran) — show done in placeholder
|
|
217
|
+
await bot.api.editMessageText(state.chatId, savedMessageId, "✅ Done").catch(() => {})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Add ✅ reaction to signal completion
|
|
221
|
+
if (savedMessageId) {
|
|
222
|
+
await bot.api
|
|
223
|
+
.setMessageReaction(state.chatId, savedMessageId, [{ type: "emoji", emoji: "👍" }])
|
|
224
|
+
.catch(() => {})
|
|
184
225
|
}
|
|
226
|
+
|
|
185
227
|
state.liveMessageId = undefined
|
|
186
228
|
state.liveText = ""
|
|
229
|
+
state.currentTool = undefined
|
|
187
230
|
}
|
|
188
231
|
}
|
|
189
232
|
}
|
|
@@ -203,7 +246,9 @@ bot.command("start", async (ctx) => {
|
|
|
203
246
|
"Commands:\n" +
|
|
204
247
|
"/sessions — list sessions\n" +
|
|
205
248
|
"/new — start a new session\n" +
|
|
206
|
-
"/info — current session details"
|
|
249
|
+
"/info — current session details\n" +
|
|
250
|
+
"/init — create AGENTS.md for current project\n" +
|
|
251
|
+
"/diff — show code changes in this session",
|
|
207
252
|
{ parse_mode: "Markdown" },
|
|
208
253
|
)
|
|
209
254
|
})
|
|
@@ -223,7 +268,7 @@ bot.command("info", async (ctx) => {
|
|
|
223
268
|
return
|
|
224
269
|
}
|
|
225
270
|
|
|
226
|
-
const res = await
|
|
271
|
+
const res = await client.session.get({ path: { id: state.sessionId } })
|
|
227
272
|
if (res.error) {
|
|
228
273
|
await ctx.reply(`❌ ${JSON.stringify(res.error)}`)
|
|
229
274
|
return
|
|
@@ -244,19 +289,89 @@ bot.command("info", async (ctx) => {
|
|
|
244
289
|
)
|
|
245
290
|
})
|
|
246
291
|
|
|
292
|
+
bot.command("init", async (ctx) => {
|
|
293
|
+
const key = getChatKey(ctx.chat.id, ctx.message?.message_thread_id)
|
|
294
|
+
let state = sessions.get(key)
|
|
295
|
+
|
|
296
|
+
if (!state) {
|
|
297
|
+
const res = await client.session.create({
|
|
298
|
+
body: { title: `Telegram ${ctx.chat.type} ${key}` },
|
|
299
|
+
})
|
|
300
|
+
if (res.error) {
|
|
301
|
+
await ctx.reply(`❌ Failed to create session: ${JSON.stringify(res.error)}`)
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
state = { sessionId: res.data.id, chatId: ctx.chat.id, threadId: ctx.message?.message_thread_id, liveText: "", lastEditMs: 0 }
|
|
305
|
+
sessions.set(key, state)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const placeholder = await ctx.reply("⏳ Analyzing project and creating AGENTS.md...")
|
|
309
|
+
|
|
310
|
+
const model = cfg.model ? parseModel(cfg.model) : undefined
|
|
311
|
+
const res = await client.session.command({
|
|
312
|
+
path: { id: state.sessionId },
|
|
313
|
+
body: { command: "init", arguments: "", ...(model ? { model: `${model.providerID}/${model.modelID}` } : {}) },
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
if (res.error) {
|
|
317
|
+
await bot.api
|
|
318
|
+
.editMessageText(ctx.chat.id, placeholder.message_id, `❌ ${JSON.stringify(res.error)}`)
|
|
319
|
+
.catch(() => {})
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
await bot.api
|
|
324
|
+
.editMessageText(ctx.chat.id, placeholder.message_id, "✅ *AGENTS.md created!*\n\nThe AI agent has analyzed your project and written configuration.", {
|
|
325
|
+
parse_mode: "Markdown",
|
|
326
|
+
})
|
|
327
|
+
.catch(() => {})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
bot.command("diff", async (ctx) => {
|
|
331
|
+
const key = getChatKey(ctx.chat.id, ctx.message?.message_thread_id)
|
|
332
|
+
const state = sessions.get(key)
|
|
333
|
+
|
|
334
|
+
if (!state) {
|
|
335
|
+
await ctx.reply("No active session. Send a message to create one.")
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const res = await client.session.diff({ path: { id: state.sessionId } })
|
|
340
|
+
if (res.error) {
|
|
341
|
+
await ctx.reply(`❌ ${JSON.stringify(res.error)}`)
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const diffs = res.data ?? []
|
|
346
|
+
|
|
347
|
+
if (diffs.length === 0) {
|
|
348
|
+
await ctx.reply("📊 No code changes in this session.")
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const totalAdd = diffs.reduce((s, d) => s + d.additions, 0)
|
|
353
|
+
const totalDel = diffs.reduce((s, d) => s + d.deletions, 0)
|
|
354
|
+
|
|
355
|
+
const fileLines = diffs.map((d) => `✏️ \`${d.file}\` (+${d.additions}/-${d.deletions})`).join("\n")
|
|
356
|
+
|
|
357
|
+
const msg = `📝 *Code Changes* — ${diffs.length} files · +${totalAdd}/-${totalDel}\n\n${fileLines}`
|
|
358
|
+
|
|
359
|
+
await ctx.reply(msg.slice(0, 4096), { parse_mode: "Markdown" })
|
|
360
|
+
})
|
|
361
|
+
|
|
247
362
|
bot.command("sessions", async (ctx) => {
|
|
248
363
|
const key = getChatKey(ctx.chat.id, ctx.message?.message_thread_id)
|
|
249
364
|
const currentState = sessions.get(key)
|
|
250
365
|
|
|
251
|
-
const res = await
|
|
366
|
+
const res = await client.session.list()
|
|
252
367
|
if (res.error) {
|
|
253
368
|
await ctx.reply(`❌ ${JSON.stringify(res.error)}`)
|
|
254
369
|
return
|
|
255
370
|
}
|
|
256
371
|
|
|
257
372
|
const list = res.data!
|
|
258
|
-
.filter((s) => !
|
|
259
|
-
.sort((a, b) => b.time.updated - a.time.updated)
|
|
373
|
+
.filter((s: any) => !s.time?.archived)
|
|
374
|
+
.sort((a: any, b: any) => b.time.updated - a.time.updated)
|
|
260
375
|
.slice(0, 10)
|
|
261
376
|
|
|
262
377
|
if (list.length === 0) {
|
|
@@ -278,7 +393,7 @@ bot.callbackQuery(/^switch:(.+)$/, async (ctx) => {
|
|
|
278
393
|
const threadId = (ctx.callbackQuery.message as any)?.message_thread_id
|
|
279
394
|
const key = getChatKey(chatId, threadId)
|
|
280
395
|
|
|
281
|
-
const res = await
|
|
396
|
+
const res = await client.session.get({ path: { id: sessionId } })
|
|
282
397
|
if (res.error) {
|
|
283
398
|
await ctx.answerCallbackQuery({ text: "❌ Session not found" })
|
|
284
399
|
return
|
|
@@ -304,7 +419,7 @@ bot.on("message:text", async (ctx) => {
|
|
|
304
419
|
let state = sessions.get(key)
|
|
305
420
|
|
|
306
421
|
if (!state) {
|
|
307
|
-
const res = await
|
|
422
|
+
const res = await client.session.create({
|
|
308
423
|
body: { title: `Telegram ${ctx.chat.type} ${key}` },
|
|
309
424
|
})
|
|
310
425
|
if (res.error) {
|
|
@@ -314,26 +429,39 @@ bot.on("message:text", async (ctx) => {
|
|
|
314
429
|
state = { sessionId: res.data.id, chatId, threadId, liveText: "", lastEditMs: 0 }
|
|
315
430
|
sessions.set(key, state)
|
|
316
431
|
|
|
317
|
-
const share = await
|
|
432
|
+
const share = await client.session.share({ path: { id: res.data.id } })
|
|
318
433
|
if (!share.error && share.data?.share?.url) {
|
|
319
434
|
await ctx.reply(`🔗 Session: ${share.data.share.url}`)
|
|
320
435
|
}
|
|
321
436
|
}
|
|
322
437
|
|
|
323
|
-
const placeholder = await ctx.reply("
|
|
438
|
+
const placeholder = await ctx.reply("🤔 Thinking\\.\\.\\.", {
|
|
439
|
+
parse_mode: "MarkdownV2",
|
|
324
440
|
...(threadId ? { message_thread_id: threadId } : {}),
|
|
325
441
|
})
|
|
326
442
|
state.liveMessageId = placeholder.message_id
|
|
327
443
|
state.liveText = ""
|
|
328
444
|
state.lastEditMs = 0
|
|
445
|
+
state.currentTool = undefined
|
|
446
|
+
|
|
447
|
+
// Show "typing..." indicator immediately and keep it alive every 4s
|
|
448
|
+
const sendTyping = () =>
|
|
449
|
+
bot.api.sendChatAction(chatId, "typing", threadId ? { message_thread_id: threadId } : {}).catch(() => {})
|
|
450
|
+
sendTyping()
|
|
451
|
+
const typingInterval = setInterval(() => {
|
|
452
|
+
if (!state.liveMessageId) return
|
|
453
|
+
sendTyping()
|
|
454
|
+
}, 4000)
|
|
329
455
|
|
|
330
456
|
const model = cfg.model ? parseModel(cfg.model) : undefined
|
|
331
457
|
|
|
332
|
-
const result = await
|
|
458
|
+
const result = await client.session.promptAsync({
|
|
333
459
|
path: { id: state.sessionId },
|
|
334
460
|
body: { parts: [{ type: "text", text }], model },
|
|
335
461
|
})
|
|
336
462
|
|
|
463
|
+
clearInterval(typingInterval)
|
|
464
|
+
|
|
337
465
|
if (result.error) {
|
|
338
466
|
await ctx.api.editMessageText(chatId, placeholder.message_id, `❌ ${JSON.stringify(result.error)}`)
|
|
339
467
|
state.liveMessageId = undefined
|