talon-agent 3.19.0 → 3.20.0
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/package.json +3 -3
- package/src/core/engine/dispatcher.ts +9 -0
- package/src/core/tasks/table.ts +18 -0
- package/src/frontend/discord/commands/definitions.ts +4 -0
- package/src/frontend/discord/commands/info.ts +2 -1
- package/src/frontend/discord/commands/router.ts +3 -1
- package/src/frontend/discord/commands/session.ts +13 -0
- package/src/frontend/telegram/commands/definitions.ts +1 -0
- package/src/frontend/telegram/commands/info.ts +2 -1
- package/src/frontend/telegram/commands/session.ts +8 -0
- package/src/frontend/telegram/userbot.ts +15 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "talon-agent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.0",
|
|
4
4
|
"description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
|
|
5
5
|
"author": "Dylan Neve",
|
|
6
6
|
"license": "MIT",
|
|
@@ -103,9 +103,9 @@
|
|
|
103
103
|
"@kilocode/sdk": "^7.2.22",
|
|
104
104
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
105
105
|
"@openai/agents": "^0.14.0",
|
|
106
|
-
"@openai/codex-sdk": "^0.
|
|
106
|
+
"@openai/codex-sdk": "^0.147.0",
|
|
107
107
|
"@opencode-ai/sdk": "^1.17.4",
|
|
108
|
-
"@playwright/mcp": "0.0.
|
|
108
|
+
"@playwright/mcp": "0.0.79",
|
|
109
109
|
"@types/cross-spawn": "^6.0.6",
|
|
110
110
|
"big-integer": "^1.6.52",
|
|
111
111
|
"cheerio": "^1.2.0",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ExecuteParams, ExecuteResult } from "../types.js";
|
|
12
12
|
import { log } from "../../util/log.js";
|
|
13
|
+
import { taskTable, type KillOutcome } from "../tasks/index.js";
|
|
13
14
|
import {
|
|
14
15
|
initWeaver,
|
|
15
16
|
type Weaver,
|
|
@@ -57,6 +58,14 @@ export function snapshot(): ThreadSnapshot[] {
|
|
|
57
58
|
return weaver?.snapshot() ?? [];
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Stop only this chat's active turn. A later message already waiting in the
|
|
63
|
+
* per-chat FIFO is intentionally left alone and runs after the stop settles.
|
|
64
|
+
*/
|
|
65
|
+
export function stopCurrentTurn(chatId: string): KillOutcome {
|
|
66
|
+
return taskTable.killRunningTurn(chatId);
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
/**
|
|
61
70
|
* Execute an AI query with full lifecycle management.
|
|
62
71
|
* Same-chat queries are serialized (FIFO) to avoid session conflicts.
|
package/src/core/tasks/table.ts
CHANGED
|
@@ -122,6 +122,24 @@ export class TaskTable {
|
|
|
122
122
|
return { ok: true };
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Request an abort for the turn currently running in one chat. Queued turns
|
|
127
|
+
* are deliberately ignored: `/stop` means "stop what is happening now",
|
|
128
|
+
* never "discard my next message".
|
|
129
|
+
*/
|
|
130
|
+
killRunningTurn(chatId: string): KillOutcome {
|
|
131
|
+
for (const [id, task] of this.live) {
|
|
132
|
+
if (
|
|
133
|
+
task.record.kind === "turn" &&
|
|
134
|
+
task.record.chatId === chatId &&
|
|
135
|
+
task.record.state === "running"
|
|
136
|
+
) {
|
|
137
|
+
return this.kill(id);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { ok: false, reason: "not-found" };
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
/**
|
|
126
144
|
* Every live task plus the bounded settled history, id-ascending.
|
|
127
145
|
* Snapshots are copies — callers can never mutate table state.
|
|
@@ -82,6 +82,10 @@ export function buildCommandDefinitions(devBuild = false): unknown[] {
|
|
|
82
82
|
.setName("reset")
|
|
83
83
|
.setDescription("Clear session and start fresh")
|
|
84
84
|
.toJSON(),
|
|
85
|
+
new SlashCommandBuilder()
|
|
86
|
+
.setName("stop")
|
|
87
|
+
.setDescription("Stop the current response")
|
|
88
|
+
.toJSON(),
|
|
85
89
|
new SlashCommandBuilder()
|
|
86
90
|
.setName("restart")
|
|
87
91
|
.setDescription("Restart the bot (admin only)")
|
|
@@ -31,7 +31,7 @@ export async function handleStart(
|
|
|
31
31
|
"",
|
|
32
32
|
"Mention me, reply to me, or DM me. Attach files, photos, voice notes — I'll read them.",
|
|
33
33
|
"",
|
|
34
|
-
"/status /reset /help",
|
|
34
|
+
"/status /stop /reset /help",
|
|
35
35
|
].join("\n"),
|
|
36
36
|
);
|
|
37
37
|
}
|
|
@@ -54,6 +54,7 @@ export async function handleHelp(
|
|
|
54
54
|
"",
|
|
55
55
|
"**Session**",
|
|
56
56
|
" /status — session info, usage, and stats",
|
|
57
|
+
" /stop — stop the current response",
|
|
57
58
|
" /metrics — aggregate performance metrics (admin)",
|
|
58
59
|
" /dream — force memory consolidation now (admin)",
|
|
59
60
|
" /ping — health check with latency",
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
handleMesh,
|
|
31
31
|
handleUsage,
|
|
32
32
|
} from "./info.js";
|
|
33
|
-
import { handleReset, handleStatus } from "./session.js";
|
|
33
|
+
import { handleReset, handleStatus, handleStop } from "./session.js";
|
|
34
34
|
import {
|
|
35
35
|
handleModel,
|
|
36
36
|
handleEffort,
|
|
@@ -143,6 +143,8 @@ async function routeSlashCommand(
|
|
|
143
143
|
return handlePulse(interaction, chatId);
|
|
144
144
|
case "reset":
|
|
145
145
|
return handleReset(interaction, gateway, chatId);
|
|
146
|
+
case "stop":
|
|
147
|
+
return handleStop(interaction, chatId);
|
|
146
148
|
case "restart":
|
|
147
149
|
return handleRestart(interaction);
|
|
148
150
|
case "metrics":
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
performSessionReset,
|
|
24
24
|
collectSessionStatus,
|
|
25
25
|
} from "../../shared/session-status.js";
|
|
26
|
+
import { stopCurrentTurn } from "../../../core/engine/dispatcher.js";
|
|
26
27
|
import { reply } from "./shared.js";
|
|
27
28
|
|
|
28
29
|
export async function handleReset(
|
|
@@ -42,6 +43,18 @@ export async function handleReset(
|
|
|
42
43
|
await reply(i, "Session cleared.", true);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
export async function handleStop(
|
|
47
|
+
i: ChatInputCommandInteraction,
|
|
48
|
+
chatId: string,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
const outcome = stopCurrentTurn(chatId);
|
|
51
|
+
await reply(
|
|
52
|
+
i,
|
|
53
|
+
outcome.ok ? "⏹ Stop requested." : "No response is currently running.",
|
|
54
|
+
true,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
export async function handleStatus(
|
|
46
59
|
i: ChatInputCommandInteraction,
|
|
47
60
|
config: TalonConfig,
|
|
@@ -26,6 +26,7 @@ export const TELEGRAM_COMMANDS: ReadonlyArray<{
|
|
|
26
26
|
{ command: "model", description: "Show or change model" },
|
|
27
27
|
{ command: "effort", description: "Set thinking effort level" },
|
|
28
28
|
{ command: "pulse", description: "Conversation engagement settings" },
|
|
29
|
+
{ command: "stop", description: "Stop the current response" },
|
|
29
30
|
{ command: "reset", description: "Clear session and start fresh" },
|
|
30
31
|
{ command: "restart", description: "Restart the bot (admin)" },
|
|
31
32
|
{ command: "usage", description: "Plan limits across every backend" },
|
|
@@ -21,7 +21,7 @@ export function registerInfoCommands(bot: Bot): void {
|
|
|
21
21
|
"Send a message, photo, doc, or voice note.",
|
|
22
22
|
"In groups, @mention or reply to activate.",
|
|
23
23
|
"",
|
|
24
|
-
"/status /reset /help",
|
|
24
|
+
"/status /stop /reset /help",
|
|
25
25
|
].join("\n"),
|
|
26
26
|
{ parse_mode: "HTML" },
|
|
27
27
|
),
|
|
@@ -40,6 +40,7 @@ export function registerInfoCommands(bot: Bot): void {
|
|
|
40
40
|
"",
|
|
41
41
|
"<b>Session</b>",
|
|
42
42
|
" /status -- session info, usage, and stats",
|
|
43
|
+
" /stop -- stop the current response",
|
|
43
44
|
" /metrics -- aggregate performance metrics (admin)",
|
|
44
45
|
" /doctor -- environment and native-module health (admin)",
|
|
45
46
|
" /dream -- force memory consolidation now",
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
performSessionReset,
|
|
21
21
|
collectSessionStatus,
|
|
22
22
|
} from "../../shared/session-status.js";
|
|
23
|
+
import { stopCurrentTurn } from "../../../core/engine/dispatcher.js";
|
|
23
24
|
import type { RegisterDeps } from "./state.js";
|
|
24
25
|
|
|
25
26
|
export function registerSessionCommands(
|
|
@@ -34,6 +35,13 @@ export function registerSessionCommands(
|
|
|
34
35
|
await ctx.reply("Session cleared.");
|
|
35
36
|
});
|
|
36
37
|
|
|
38
|
+
bot.command("stop", async (ctx) => {
|
|
39
|
+
const outcome = stopCurrentTurn(String(ctx.chat.id));
|
|
40
|
+
await ctx.reply(
|
|
41
|
+
outcome.ok ? "⏹ Stop requested." : "No response is currently running.",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
37
45
|
bot.command("status", async (ctx) => {
|
|
38
46
|
const cid = String(ctx.chat.id);
|
|
39
47
|
const s = await collectSessionStatus(
|
|
@@ -134,10 +134,23 @@ function startConnectionMonitor(): void {
|
|
|
134
134
|
if (reconnectTimer) return;
|
|
135
135
|
reconnectTimer = setInterval(async () => {
|
|
136
136
|
if (!client) return;
|
|
137
|
-
if (client.connected) return;
|
|
138
137
|
if (reconnecting) return; // prevent overlapping reconnect attempts
|
|
139
|
-
reconnecting = true;
|
|
140
138
|
|
|
139
|
+
// GramJS keeps `client.connected === true` even after the underlying
|
|
140
|
+
// socket has silently died, so a real round-trip is the only reliable
|
|
141
|
+
// liveness signal. Without it the userbot can go stale for hours/days
|
|
142
|
+
// while the monitor believes it is still connected.
|
|
143
|
+
if (client.connected) {
|
|
144
|
+
try {
|
|
145
|
+
// A full API round-trip; throws if the socket is silently dead.
|
|
146
|
+
await client.getMe();
|
|
147
|
+
return; // genuinely alive
|
|
148
|
+
} catch {
|
|
149
|
+
logWarn("userbot", "Liveness check failed — treating socket as dead.");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
reconnecting = true;
|
|
141
154
|
logWarn("userbot", "Connection lost, attempting reconnect...");
|
|
142
155
|
try {
|
|
143
156
|
await client.connect();
|