@blunking/codexlink 0.1.16 → 0.1.17
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 +5 -4
- package/start-codex-agent.ps1 +28 -12
- package/telegram-console-input.ps1 +151 -0
- package/telegram-plugin/README.md +4 -3
- package/telegram-plugin/lib/app-server-client.js +141 -7
- package/telegram-plugin/lib/bridge.js +203 -15
- package/telegram-plugin/lib/codex.js +226 -7
- package/telegram-plugin/lib/env.js +1 -0
- package/telegram-plugin/lib/paths.js +2 -1
- package/telegram-plugin/lib/telegram.js +15 -0
- package/telegram-title-embed.ps1 +133 -47
- package/telegram-title-watcher.ps1 +8 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blunking/codexlink",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "BLUN CLI launcher with Telegram channel support for one visible session.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
"telegram-title-embed.ps1",
|
|
22
22
|
"telegram-status.ps1",
|
|
23
23
|
"telegram-doctor.ps1",
|
|
24
|
-
"telegram-setup.ps1",
|
|
25
|
-
"telegram-
|
|
26
|
-
|
|
24
|
+
"telegram-setup.ps1",
|
|
25
|
+
"telegram-console-input.ps1",
|
|
26
|
+
"telegram-title-watcher.ps1"
|
|
27
|
+
],
|
|
27
28
|
"keywords": [
|
|
28
29
|
"blun",
|
|
29
30
|
"codexlink",
|
package/start-codex-agent.ps1
CHANGED
|
@@ -469,12 +469,17 @@ if ($telegramEnvOverride) {
|
|
|
469
469
|
$commonOverrides += $telegramEnvOverride
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
-
$codexArgs = @()
|
|
473
|
-
|
|
474
|
-
if ($profile.
|
|
475
|
-
$codexArgs += "
|
|
476
|
-
$codexArgs +=
|
|
477
|
-
}
|
|
472
|
+
$codexArgs = @()
|
|
473
|
+
|
|
474
|
+
if ($profile.model) {
|
|
475
|
+
$codexArgs += "--model"
|
|
476
|
+
$codexArgs += $profile.model
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if ($profile.reasoning_effort) {
|
|
480
|
+
$codexArgs += "-c"
|
|
481
|
+
$codexArgs += ("model_reasoning_effort=""" + $profile.reasoning_effort + """")
|
|
482
|
+
}
|
|
478
483
|
|
|
479
484
|
if ($profile.personality) {
|
|
480
485
|
$codexArgs += "-c"
|
|
@@ -749,20 +754,28 @@ if ($useRemoteAppServer) {
|
|
|
749
754
|
if ($loadedIds.Count -gt 0) {
|
|
750
755
|
$activeThreadId = [string]$loadedIds[$loadedIds.Count - 1]
|
|
751
756
|
$bestThreadScore = [double]::NegativeInfinity
|
|
757
|
+
$runtimeStartedAtMs = 0
|
|
758
|
+
try {
|
|
759
|
+
$runtimeStartedAtMs = [DateTimeOffset]::Parse([string]$currentRuntime.started_at).ToUnixTimeMilliseconds()
|
|
760
|
+
} catch {
|
|
761
|
+
$runtimeStartedAtMs = 0
|
|
762
|
+
}
|
|
752
763
|
foreach ($candidate in $loadedIds) {
|
|
753
764
|
$candidateThreadId = [string]$candidate
|
|
754
765
|
if ([string]::IsNullOrWhiteSpace($candidateThreadId)) {
|
|
755
766
|
continue
|
|
756
767
|
}
|
|
757
768
|
$threadScore = 0.0
|
|
769
|
+
$threadCreatedAtMs = 0.0
|
|
758
770
|
try {
|
|
759
771
|
$candidateInfo = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "read-thread", "--ws-url", $telegramAppServerWsUrl, "--thread-id", $candidateThreadId) -Attempts 1 -DelayMs 0
|
|
760
772
|
$thread = $candidateInfo.response.result.thread
|
|
761
773
|
if ($null -ne $thread -and $null -ne $thread.createdAt) {
|
|
762
|
-
$
|
|
763
|
-
if ($
|
|
764
|
-
$
|
|
774
|
+
$threadCreatedAtMs = [double]$thread.createdAt
|
|
775
|
+
if ($threadCreatedAtMs -gt 0 -and $threadCreatedAtMs -lt 1000000000000) {
|
|
776
|
+
$threadCreatedAtMs = $threadCreatedAtMs * 1000
|
|
765
777
|
}
|
|
778
|
+
$threadScore = $threadCreatedAtMs
|
|
766
779
|
}
|
|
767
780
|
$threadSource = ""
|
|
768
781
|
$threadStatusType = ""
|
|
@@ -773,11 +786,14 @@ if ($useRemoteAppServer) {
|
|
|
773
786
|
$threadStatusType = ([string]$thread.status.type).ToLowerInvariant()
|
|
774
787
|
}
|
|
775
788
|
if ($threadSource -eq "cli" -and $threadStatusType -eq "active") {
|
|
776
|
-
$threadScore +=
|
|
789
|
+
$threadScore += 4000000000000000
|
|
777
790
|
} elseif ($threadStatusType -eq "active") {
|
|
778
|
-
$threadScore +=
|
|
791
|
+
$threadScore += 3000000000000000
|
|
779
792
|
} elseif ($threadSource -eq "cli") {
|
|
780
|
-
$threadScore +=
|
|
793
|
+
$threadScore += 2000000000000000
|
|
794
|
+
}
|
|
795
|
+
if ($runtimeStartedAtMs -gt 0 -and $threadCreatedAtMs -ge ($runtimeStartedAtMs - 120000)) {
|
|
796
|
+
$threadScore += 1000000000000000
|
|
781
797
|
}
|
|
782
798
|
} catch {
|
|
783
799
|
$threadScore = 0.0
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory = $true)]
|
|
3
|
+
[int]$TargetPid,
|
|
4
|
+
|
|
5
|
+
[string]$Text = "",
|
|
6
|
+
|
|
7
|
+
[switch]$ClearBefore,
|
|
8
|
+
|
|
9
|
+
[switch]$Submit
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
$ErrorActionPreference = "Stop"
|
|
13
|
+
|
|
14
|
+
$typeName = "ConsoleInputWriter"
|
|
15
|
+
$assemblyDir = Join-Path $env:TEMP "blun-codexlink"
|
|
16
|
+
$assemblyPath = Join-Path $assemblyDir "console-input-writer-v4.dll"
|
|
17
|
+
|
|
18
|
+
$source = @"
|
|
19
|
+
using System;
|
|
20
|
+
using System.Runtime.InteropServices;
|
|
21
|
+
|
|
22
|
+
public static class ConsoleInputWriter {
|
|
23
|
+
private const int STD_INPUT_HANDLE = -10;
|
|
24
|
+
private const short KEY_EVENT = 0x0001;
|
|
25
|
+
private const ushort VK_RETURN = 0x0D;
|
|
26
|
+
private const ushort VK_BACK = 0x08;
|
|
27
|
+
private const ushort VK_U = 0x55;
|
|
28
|
+
private const ushort SCAN_RETURN = 0x1C;
|
|
29
|
+
private const ushort SCAN_U = 0x16;
|
|
30
|
+
private const uint LEFT_CTRL_PRESSED = 0x0008;
|
|
31
|
+
private const uint GENERIC_READ = 0x80000000;
|
|
32
|
+
private const uint GENERIC_WRITE = 0x40000000;
|
|
33
|
+
private const uint FILE_SHARE_READ = 0x00000001;
|
|
34
|
+
private const uint FILE_SHARE_WRITE = 0x00000002;
|
|
35
|
+
private const uint OPEN_EXISTING = 3;
|
|
36
|
+
|
|
37
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
38
|
+
public struct KEY_EVENT_RECORD {
|
|
39
|
+
[MarshalAs(UnmanagedType.Bool)]
|
|
40
|
+
public bool bKeyDown;
|
|
41
|
+
public ushort wRepeatCount;
|
|
42
|
+
public ushort wVirtualKeyCode;
|
|
43
|
+
public ushort wVirtualScanCode;
|
|
44
|
+
public char UnicodeChar;
|
|
45
|
+
public uint dwControlKeyState;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[StructLayout(LayoutKind.Explicit)]
|
|
49
|
+
public struct INPUT_RECORD {
|
|
50
|
+
[FieldOffset(0)]
|
|
51
|
+
public short EventType;
|
|
52
|
+
[FieldOffset(4)]
|
|
53
|
+
public KEY_EVENT_RECORD KeyEvent;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[DllImport("kernel32.dll", SetLastError=true)]
|
|
57
|
+
private static extern bool AttachConsole(uint dwProcessId);
|
|
58
|
+
|
|
59
|
+
[DllImport("kernel32.dll", SetLastError=true)]
|
|
60
|
+
private static extern bool FreeConsole();
|
|
61
|
+
|
|
62
|
+
[DllImport("kernel32.dll", SetLastError=true)]
|
|
63
|
+
private static extern IntPtr GetStdHandle(int nStdHandle);
|
|
64
|
+
|
|
65
|
+
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
66
|
+
private static extern IntPtr CreateFileW(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
|
|
67
|
+
|
|
68
|
+
[DllImport("kernel32.dll", SetLastError=true)]
|
|
69
|
+
private static extern bool CloseHandle(IntPtr hObject);
|
|
70
|
+
|
|
71
|
+
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
72
|
+
private static extern bool WriteConsoleInputW(IntPtr hConsoleInput, INPUT_RECORD[] lpBuffer, uint nLength, out uint lpNumberOfEventsWritten);
|
|
73
|
+
|
|
74
|
+
public static void WriteText(int targetPid, string text, bool clearBefore, bool submit) {
|
|
75
|
+
FreeConsole();
|
|
76
|
+
if (!AttachConsole((uint)targetPid)) {
|
|
77
|
+
throw new InvalidOperationException("AttachConsole failed: " + Marshal.GetLastWin32Error());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
IntPtr input = CreateFileW("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
|
|
81
|
+
if (input == IntPtr.Zero || input == new IntPtr(-1)) {
|
|
82
|
+
input = GetStdHandle(STD_INPUT_HANDLE);
|
|
83
|
+
}
|
|
84
|
+
if (input == IntPtr.Zero || input == new IntPtr(-1)) {
|
|
85
|
+
throw new InvalidOperationException("Open console input failed: " + Marshal.GetLastWin32Error());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
if (clearBefore) {
|
|
90
|
+
WriteKey(input, (char)21, VK_U, SCAN_U, LEFT_CTRL_PRESSED);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
foreach (char ch in text) {
|
|
94
|
+
WriteKey(input, ch, 0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (submit) {
|
|
98
|
+
WriteKey(input, '\r', VK_RETURN, SCAN_RETURN);
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
CloseHandle(input);
|
|
102
|
+
FreeConsole();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private static void WriteKey(IntPtr input, char ch, ushort virtualKey) {
|
|
107
|
+
WriteKey(input, ch, virtualKey, 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private static void WriteKey(IntPtr input, char ch, ushort virtualKey, ushort virtualScanCode) {
|
|
111
|
+
WriteKey(input, ch, virtualKey, virtualScanCode, 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private static void WriteKey(IntPtr input, char ch, ushort virtualKey, ushort virtualScanCode, uint controlKeyState) {
|
|
115
|
+
INPUT_RECORD[] records = new INPUT_RECORD[2];
|
|
116
|
+
records[0].EventType = KEY_EVENT;
|
|
117
|
+
records[0].KeyEvent.bKeyDown = true;
|
|
118
|
+
records[0].KeyEvent.wRepeatCount = 1;
|
|
119
|
+
records[0].KeyEvent.wVirtualKeyCode = virtualKey;
|
|
120
|
+
records[0].KeyEvent.wVirtualScanCode = virtualScanCode;
|
|
121
|
+
records[0].KeyEvent.UnicodeChar = ch;
|
|
122
|
+
records[0].KeyEvent.dwControlKeyState = controlKeyState;
|
|
123
|
+
|
|
124
|
+
records[1].EventType = KEY_EVENT;
|
|
125
|
+
records[1].KeyEvent.bKeyDown = false;
|
|
126
|
+
records[1].KeyEvent.wRepeatCount = 1;
|
|
127
|
+
records[1].KeyEvent.wVirtualKeyCode = virtualKey;
|
|
128
|
+
records[1].KeyEvent.wVirtualScanCode = virtualScanCode;
|
|
129
|
+
records[1].KeyEvent.UnicodeChar = ch;
|
|
130
|
+
records[1].KeyEvent.dwControlKeyState = controlKeyState;
|
|
131
|
+
|
|
132
|
+
uint written;
|
|
133
|
+
if (!WriteConsoleInputW(input, records, (uint)records.Length, out written) || written != records.Length) {
|
|
134
|
+
throw new InvalidOperationException("WriteConsoleInputW failed: " + Marshal.GetLastWin32Error());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
"@
|
|
139
|
+
|
|
140
|
+
if (-not ($typeName -as [type])) {
|
|
141
|
+
if (Test-Path $assemblyPath) {
|
|
142
|
+
Add-Type -Path $assemblyPath
|
|
143
|
+
} else {
|
|
144
|
+
New-Item -ItemType Directory -Path $assemblyDir -Force | Out-Null
|
|
145
|
+
Add-Type -TypeDefinition $source -OutputAssembly $assemblyPath -OutputType Library
|
|
146
|
+
Add-Type -Path $assemblyPath
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
$normalizedText = $Text -replace "`r`n", " " -replace "`n", " " -replace "`r", " "
|
|
151
|
+
[ConsoleInputWriter]::WriteText($TargetPid, $normalizedText, [bool]$ClearBefore, [bool]$Submit)
|
|
@@ -10,8 +10,9 @@ It is intentionally **not** an autonomous answer bot.
|
|
|
10
10
|
- stores inbound and outbound history under a local state directory
|
|
11
11
|
- keeps private chats and group threads separated
|
|
12
12
|
- binds a live thread id
|
|
13
|
-
- injects
|
|
14
|
-
-
|
|
13
|
+
- injects direct/private/lane Telegram messages into the active app-server thread
|
|
14
|
+
- steers active turns when the app-server supports it
|
|
15
|
+
- keeps queued Telegram messages visible when they are waiting instead of ready to deliver
|
|
15
16
|
- sends explicit manual replies from the visible operator session
|
|
16
17
|
- keeps ambient group noise queued unless it is relevant to that operator
|
|
17
18
|
- lets escalation-style messages bypass the normal idle queue
|
|
@@ -47,7 +48,7 @@ Copy `.env.example` to `.env` in the state directory or export env vars:
|
|
|
47
48
|
|
|
48
49
|
- `BLUN_TELEGRAM_AGENT_NAME`
|
|
49
50
|
- `BLUN_TELEGRAM_BOT_TOKEN`
|
|
50
|
-
- `BLUN_TELEGRAM_ALLOWED_CHAT_ID` (`chatId` or comma-separated list like `
|
|
51
|
+
- `BLUN_TELEGRAM_ALLOWED_CHAT_ID` (`chatId` or comma-separated list like `123456789,-1001234567890`)
|
|
51
52
|
- `BLUN_TELEGRAM_CODEX_BIN`
|
|
52
53
|
- `BLUN_TELEGRAM_THREAD_ID`
|
|
53
54
|
- `BLUN_TELEGRAM_RESUME_TIMEOUT_MS`
|
|
@@ -55,12 +55,44 @@ function extractTurnId(response) {
|
|
|
55
55
|
|| "";
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function extractActiveTurnId(turnsResponse) {
|
|
59
|
+
const turns = Array.isArray(turnsResponse?.result?.data) ? turnsResponse.result.data : [];
|
|
60
|
+
const active = turns.find((turn) => String(turn?.status || "").trim() === "inProgress");
|
|
61
|
+
return String(active?.id || "").trim();
|
|
62
|
+
}
|
|
63
|
+
|
|
58
64
|
function extractThreadPath(response) {
|
|
59
65
|
return response?.result?.thread?.path
|
|
60
66
|
|| response?.result?.path
|
|
61
67
|
|| "";
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
function normalizeUserInput(input) {
|
|
71
|
+
const items = Array.isArray(input) ? input : [];
|
|
72
|
+
return items.map((item) => {
|
|
73
|
+
if (!item || typeof item !== "object") {
|
|
74
|
+
return item;
|
|
75
|
+
}
|
|
76
|
+
if (item.type === "text" && !Array.isArray(item.text_elements)) {
|
|
77
|
+
return {
|
|
78
|
+
...item,
|
|
79
|
+
text_elements: []
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return item;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildTextInput(text) {
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text,
|
|
91
|
+
text_elements: []
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
64
96
|
export class AppServerClient {
|
|
65
97
|
constructor(wsUrl, options = {}) {
|
|
66
98
|
this.wsUrl = normalizeWsUrl(wsUrl);
|
|
@@ -220,14 +252,12 @@ export async function startThreadOverWs(options) {
|
|
|
220
252
|
export async function startTextTurnOverWs(options) {
|
|
221
253
|
const client = new AppServerClient(options.wsUrl, { timeoutMs: options.timeoutMs || 20000 });
|
|
222
254
|
try {
|
|
255
|
+
const input = Array.isArray(options.input) && options.input.length > 0
|
|
256
|
+
? normalizeUserInput(options.input)
|
|
257
|
+
: buildTextInput(options.text);
|
|
223
258
|
const response = await client.request("turn/start", {
|
|
224
259
|
threadId: options.threadId,
|
|
225
|
-
input
|
|
226
|
-
{
|
|
227
|
-
type: "text",
|
|
228
|
-
text: options.text
|
|
229
|
-
}
|
|
230
|
-
],
|
|
260
|
+
input,
|
|
231
261
|
model: options.model || null,
|
|
232
262
|
effort: options.effort || null,
|
|
233
263
|
personality: options.personality || null
|
|
@@ -256,11 +286,115 @@ export async function startTextTurnOverWs(options) {
|
|
|
256
286
|
}
|
|
257
287
|
}
|
|
258
288
|
|
|
289
|
+
export async function startOrSteerTextTurnOverWs(options) {
|
|
290
|
+
const client = new AppServerClient(options.wsUrl, { timeoutMs: options.timeoutMs || 20000 });
|
|
291
|
+
try {
|
|
292
|
+
const input = Array.isArray(options.input) && options.input.length > 0
|
|
293
|
+
? normalizeUserInput(options.input)
|
|
294
|
+
: buildTextInput(options.text);
|
|
295
|
+
|
|
296
|
+
let activeTurnId = "";
|
|
297
|
+
try {
|
|
298
|
+
const turnsResponse = await client.request("thread/turns/list", {
|
|
299
|
+
threadId: options.threadId,
|
|
300
|
+
limit: 8,
|
|
301
|
+
itemsView: "notLoaded"
|
|
302
|
+
}, { timeoutMs: Math.min(options.timeoutMs || 20000, 5000) });
|
|
303
|
+
activeTurnId = extractActiveTurnId(turnsResponse);
|
|
304
|
+
} catch {
|
|
305
|
+
activeTurnId = "";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (activeTurnId) {
|
|
309
|
+
try {
|
|
310
|
+
const steerResponse = await client.request("turn/steer", {
|
|
311
|
+
threadId: options.threadId,
|
|
312
|
+
expectedTurnId: activeTurnId,
|
|
313
|
+
input,
|
|
314
|
+
responsesapiClientMetadata: {
|
|
315
|
+
source: "telegram"
|
|
316
|
+
}
|
|
317
|
+
}, { timeoutMs: options.timeoutMs || 20000 });
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
ok: true,
|
|
321
|
+
busy: false,
|
|
322
|
+
steered: true,
|
|
323
|
+
turnId: extractTurnId(steerResponse) || activeTurnId,
|
|
324
|
+
response: steerResponse
|
|
325
|
+
};
|
|
326
|
+
} catch (error) {
|
|
327
|
+
const details = `${error?.message || error}`.toLowerCase();
|
|
328
|
+
const notSteerable = details.includes("activeturnnotsteerable")
|
|
329
|
+
|| details.includes("not steerable")
|
|
330
|
+
|| details.includes("cannot accept same-turn steering");
|
|
331
|
+
const staleTurn = details.includes("expectedturnid")
|
|
332
|
+
|| details.includes("precondition")
|
|
333
|
+
|| details.includes("does not match")
|
|
334
|
+
|| details.includes("no active turn");
|
|
335
|
+
if (notSteerable) {
|
|
336
|
+
return {
|
|
337
|
+
ok: false,
|
|
338
|
+
busy: true,
|
|
339
|
+
steered: true,
|
|
340
|
+
turnId: activeTurnId,
|
|
341
|
+
error
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (!staleTurn) {
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
busy: true,
|
|
348
|
+
steered: true,
|
|
349
|
+
turnId: activeTurnId,
|
|
350
|
+
error
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const response = await client.request("turn/start", {
|
|
357
|
+
threadId: options.threadId,
|
|
358
|
+
input,
|
|
359
|
+
model: options.model || null,
|
|
360
|
+
effort: options.effort || null,
|
|
361
|
+
personality: options.personality || null,
|
|
362
|
+
responsesapiClientMetadata: {
|
|
363
|
+
source: "telegram"
|
|
364
|
+
}
|
|
365
|
+
}, { timeoutMs: options.timeoutMs || 20000 });
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
ok: true,
|
|
369
|
+
busy: false,
|
|
370
|
+
steered: false,
|
|
371
|
+
turnId: extractTurnId(response),
|
|
372
|
+
response
|
|
373
|
+
};
|
|
374
|
+
} catch (error) {
|
|
375
|
+
const details = `${error?.message || error}`.toLowerCase();
|
|
376
|
+
const busy = details.includes("active turn")
|
|
377
|
+
|| details.includes("cannot accept")
|
|
378
|
+
|| details.includes("already running")
|
|
379
|
+
|| details.includes("busy");
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
ok: false,
|
|
383
|
+
busy,
|
|
384
|
+
steered: false,
|
|
385
|
+
error
|
|
386
|
+
};
|
|
387
|
+
} finally {
|
|
388
|
+
await client.close();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
259
392
|
export async function readThreadOverWs(options) {
|
|
260
393
|
const client = new AppServerClient(options.wsUrl, { timeoutMs: options.timeoutMs || 10000 });
|
|
261
394
|
try {
|
|
262
395
|
const response = await client.request("thread/read", {
|
|
263
|
-
threadId: options.threadId
|
|
396
|
+
threadId: options.threadId,
|
|
397
|
+
includeTurns: Boolean(options.includeTurns)
|
|
264
398
|
}, { timeoutMs: options.timeoutMs || 10000 });
|
|
265
399
|
const threadId = extractThreadId(response);
|
|
266
400
|
return {
|