@blunking/codexlink 0.1.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/LICENSE +21 -0
- package/README.md +127 -0
- package/bin/blun-codex.js +26 -0
- package/blun-codex.cmd +3 -0
- package/blun-codex.ps1 +110 -0
- package/package.json +37 -0
- package/profiles/default.json +20 -0
- package/start-codex-agent.ps1 +727 -0
- package/start-codex.cmd +2 -0
- package/telegram-doctor.ps1 +125 -0
- package/telegram-plugin/.codex-plugin/plugin.json +6 -0
- package/telegram-plugin/.env.example +9 -0
- package/telegram-plugin/.mcp.json +8 -0
- package/telegram-plugin/README.md +68 -0
- package/telegram-plugin/app-server-cli.js +98 -0
- package/telegram-plugin/dispatcher.js +37 -0
- package/telegram-plugin/lib/app-server-client.js +290 -0
- package/telegram-plugin/lib/bridge.js +944 -0
- package/telegram-plugin/lib/codex.js +185 -0
- package/telegram-plugin/lib/env.js +46 -0
- package/telegram-plugin/lib/paths.js +45 -0
- package/telegram-plugin/lib/sidecars.js +142 -0
- package/telegram-plugin/lib/storage.js +49 -0
- package/telegram-plugin/lib/telegram.js +37 -0
- package/telegram-plugin/package.json +10 -0
- package/telegram-plugin/poller.js +37 -0
- package/telegram-plugin/responder.js +37 -0
- package/telegram-plugin/server.js +140 -0
- package/telegram-plugin/sidecar-manager.js +8 -0
- package/telegram-status.ps1 +160 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { bindCurrentThread, bridgeStatus, injectNext, listQueue, pollOnce, relayRepliesOnce, reply, tailActivity } from "./lib/bridge.js";
|
|
6
|
+
import { loadConfig } from "./lib/env.js";
|
|
7
|
+
import { ensureStateLayout } from "./lib/paths.js";
|
|
8
|
+
import { ensureBackgroundSidecars } from "./lib/sidecars.js";
|
|
9
|
+
|
|
10
|
+
ensureStateLayout();
|
|
11
|
+
ensureBackgroundSidecars(loadConfig());
|
|
12
|
+
|
|
13
|
+
function textResult(value) {
|
|
14
|
+
return {
|
|
15
|
+
content: [
|
|
16
|
+
{
|
|
17
|
+
type: "text",
|
|
18
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const server = new Server(
|
|
25
|
+
{
|
|
26
|
+
name: "codexlink-telegram",
|
|
27
|
+
version: "0.1.0"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
capabilities: {
|
|
31
|
+
tools: {}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
37
|
+
tools: [
|
|
38
|
+
{
|
|
39
|
+
name: "bridge_status",
|
|
40
|
+
description: "Show BLUN Telegram bridge status, bound thread, queue depth, and recent activity pointers.",
|
|
41
|
+
inputSchema: { type: "object", properties: {} }
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "bridge_bind_current_thread",
|
|
45
|
+
description: "Bind the real Codex thread id that queued Telegram messages should inject into. If omitted, CODEX_THREAD_ID is used.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
thread_id: { type: "string", description: "Explicit Codex thread UUID to bind." }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "bridge_poll_once",
|
|
55
|
+
description: "Poll Telegram one time and append any allowed inbound messages to the BLUN queue.",
|
|
56
|
+
inputSchema: { type: "object", properties: {} }
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "bridge_list_queue",
|
|
60
|
+
description: "List the most recent queued or processed bridge messages.",
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
limit: { type: "number", description: "How many queue items to return. Default 10." }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "bridge_inject_next",
|
|
70
|
+
description: "Inject the next queued Telegram message into the bound real Codex thread. If that thread is busy, the message remains queued.",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
thread_id: { type: "string", description: "Optional explicit thread id override for this injection." }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "bridge_reply",
|
|
80
|
+
description: "Send an explicit manual Telegram reply from the real operator/CLI.",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
text: { type: "string", description: "Reply text to send." },
|
|
85
|
+
chat_id: { type: "string", description: "Optional chat id override. Defaults to the latest inbound chat." },
|
|
86
|
+
reply_to_message_id: { type: "string", description: "Optional Telegram message id to reply under." },
|
|
87
|
+
telegram_thread_id: { type: "string", description: "Optional Telegram topic/thread id for forum-style group topics." }
|
|
88
|
+
},
|
|
89
|
+
required: ["text"]
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "bridge_relay_once",
|
|
94
|
+
description: "Read the active Codex session file and relay any completed Telegram-originated final answers back to Telegram.",
|
|
95
|
+
inputSchema: { type: "object", properties: {} }
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "bridge_tail_activity",
|
|
99
|
+
description: "Read the last lines from the local BLUN Telegram bridge activity log.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
lines: { type: "number", description: "Number of lines to read. Default 20." }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
111
|
+
const args = request.params.arguments || {};
|
|
112
|
+
switch (request.params.name) {
|
|
113
|
+
case "bridge_status":
|
|
114
|
+
return textResult(bridgeStatus());
|
|
115
|
+
case "bridge_bind_current_thread":
|
|
116
|
+
return textResult(bindCurrentThread(args.thread_id || ""));
|
|
117
|
+
case "bridge_poll_once":
|
|
118
|
+
return textResult(await pollOnce());
|
|
119
|
+
case "bridge_list_queue":
|
|
120
|
+
return textResult(listQueue(Number(args.limit || 10)));
|
|
121
|
+
case "bridge_inject_next":
|
|
122
|
+
return textResult(await injectNext(args.thread_id || ""));
|
|
123
|
+
case "bridge_reply":
|
|
124
|
+
return textResult(
|
|
125
|
+
await reply(String(args.text || ""), {
|
|
126
|
+
chatId: args.chat_id || "",
|
|
127
|
+
replyToMessageId: args.reply_to_message_id || "",
|
|
128
|
+
telegramThreadId: args.telegram_thread_id || ""
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
case "bridge_relay_once":
|
|
132
|
+
return textResult(await relayRepliesOnce());
|
|
133
|
+
case "bridge_tail_activity":
|
|
134
|
+
return textResult(tailActivity(Number(args.lines || 20)));
|
|
135
|
+
default:
|
|
136
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await server.connect(new StdioServerTransport());
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadConfig } from "./lib/env.js";
|
|
3
|
+
import { ensureStateLayout } from "./lib/paths.js";
|
|
4
|
+
import { ensureBackgroundSidecars } from "./lib/sidecars.js";
|
|
5
|
+
|
|
6
|
+
ensureStateLayout();
|
|
7
|
+
const result = ensureBackgroundSidecars(loadConfig());
|
|
8
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$Profile = "default"
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
$ErrorActionPreference = "Stop"
|
|
6
|
+
|
|
7
|
+
function Try-ReadJson {
|
|
8
|
+
param([string]$Path)
|
|
9
|
+
if (-not (Test-Path $Path)) { return $null }
|
|
10
|
+
try { return Get-Content -Raw -Path $Path | ConvertFrom-Json } catch { return $null }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function Read-DotEnvFile {
|
|
14
|
+
param([string]$Path)
|
|
15
|
+
$values = @{}
|
|
16
|
+
if (-not (Test-Path $Path)) { return $values }
|
|
17
|
+
foreach ($line in (Get-Content -Path $Path)) {
|
|
18
|
+
if (-not $line) { continue }
|
|
19
|
+
if ($line.Trim().StartsWith("#")) { continue }
|
|
20
|
+
$parts = $line -split "=", 2
|
|
21
|
+
if ($parts.Count -ne 2) { continue }
|
|
22
|
+
$values[$parts[0].Trim()] = $parts[1]
|
|
23
|
+
}
|
|
24
|
+
return $values
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function Test-PidAlive {
|
|
28
|
+
param([int]$ProcId)
|
|
29
|
+
if ($ProcId -le 0) { return $false }
|
|
30
|
+
return $null -ne (Get-Process -Id $ProcId -ErrorAction SilentlyContinue)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Resolve-ConfiguredPath {
|
|
34
|
+
param([string]$Value, [string]$RuntimeRoot)
|
|
35
|
+
if (-not $Value) { return "" }
|
|
36
|
+
$expanded = [Environment]::ExpandEnvironmentVariables($Value)
|
|
37
|
+
if ([System.IO.Path]::IsPathRooted($expanded)) { return $expanded }
|
|
38
|
+
return [System.IO.Path]::GetFullPath((Join-Path $RuntimeRoot $expanded))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function Get-TelegramPluginRoot {
|
|
42
|
+
param([string]$RuntimeRoot)
|
|
43
|
+
|
|
44
|
+
$candidates = @()
|
|
45
|
+
if ($env:BLUN_CODEX_TELEGRAM_PLUGIN_ROOT) {
|
|
46
|
+
$candidates += $env:BLUN_CODEX_TELEGRAM_PLUGIN_ROOT
|
|
47
|
+
}
|
|
48
|
+
$candidates += (Join-Path $RuntimeRoot "telegram-plugin")
|
|
49
|
+
|
|
50
|
+
foreach ($candidate in $candidates) {
|
|
51
|
+
if (-not $candidate) { continue }
|
|
52
|
+
if ((Test-Path (Join-Path $candidate "app-server-cli.js")) -and (Test-Path (Join-Path $candidate "sidecar-manager.js"))) {
|
|
53
|
+
return $candidate
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return $null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$runtimeRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
61
|
+
$profilePath = Join-Path $runtimeRoot ("profiles\" + $Profile.ToLower() + ".json")
|
|
62
|
+
$profileJson = Try-ReadJson -Path $profilePath
|
|
63
|
+
$profileAgent = if ($profileJson -and $profileJson.agent_name) { [string]$profileJson.agent_name } else { $Profile.ToLower() }
|
|
64
|
+
$runtimeDir = Join-Path $env:USERPROFILE (".codex\\runtimes\\" + $profileAgent)
|
|
65
|
+
$stateDir = if ($profileJson -and $profileJson.telegram -and $profileJson.telegram.state_dir) {
|
|
66
|
+
Resolve-ConfiguredPath -Value ([string]$profileJson.telegram.state_dir) -RuntimeRoot $runtimeRoot
|
|
67
|
+
} else {
|
|
68
|
+
Join-Path $env:USERPROFILE (".codex\\channels\\telegram-" + $profileAgent)
|
|
69
|
+
}
|
|
70
|
+
$currentRuntime = Try-ReadJson -Path (Join-Path $runtimeDir "current-remote-runtime.json")
|
|
71
|
+
$state = Try-ReadJson -Path (Join-Path $stateDir "state.json")
|
|
72
|
+
$envFile = Read-DotEnvFile -Path (Join-Path $stateDir ".env")
|
|
73
|
+
$loadedThreads = @()
|
|
74
|
+
$queue = @($state.queue)
|
|
75
|
+
$queued = @($queue | Where-Object { $_.status -eq "queued" })
|
|
76
|
+
$submitted = @($queue | Where-Object { $_.status -eq "submitted" })
|
|
77
|
+
$delivered = @($queue | Where-Object { $_.status -eq "delivered" })
|
|
78
|
+
$errors = @($queue | Where-Object { $_.status -eq "error" })
|
|
79
|
+
$pendingReplies = @($state.pendingReplies | Where-Object { -not $_.sentAt -and $_.status -ne "error" })
|
|
80
|
+
$pollerPid = if (Test-Path (Join-Path $stateDir "poller.pid")) { (Get-Content -Raw (Join-Path $stateDir "poller.pid")).Trim() } else { $null }
|
|
81
|
+
$dispatcherPid = if (Test-Path (Join-Path $stateDir "dispatcher.pid")) { (Get-Content -Raw (Join-Path $stateDir "dispatcher.pid")).Trim() } else { $null }
|
|
82
|
+
$responderPid = if (Test-Path (Join-Path $stateDir "responder.pid")) { (Get-Content -Raw (Join-Path $stateDir "responder.pid")).Trim() } else { $null }
|
|
83
|
+
$stateThreadId = if ($state.currentThreadId) { [string]$state.currentThreadId } else { "" }
|
|
84
|
+
$telegramPluginRoot = Get-TelegramPluginRoot -RuntimeRoot $runtimeRoot
|
|
85
|
+
|
|
86
|
+
if ($currentRuntime) {
|
|
87
|
+
if ($stateThreadId) {
|
|
88
|
+
if ($currentRuntime.PSObject.Properties.Name.Contains("thread_id")) {
|
|
89
|
+
$currentRuntime.thread_id = $stateThreadId
|
|
90
|
+
} else {
|
|
91
|
+
$currentRuntime | Add-Member -NotePropertyName "thread_id" -NotePropertyValue $stateThreadId
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if ($pollerPid) {
|
|
95
|
+
if ($currentRuntime.PSObject.Properties.Name.Contains("poller_pid")) {
|
|
96
|
+
$currentRuntime.poller_pid = $pollerPid
|
|
97
|
+
} else {
|
|
98
|
+
$currentRuntime | Add-Member -NotePropertyName "poller_pid" -NotePropertyValue $pollerPid
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if ($dispatcherPid) {
|
|
102
|
+
if ($currentRuntime.PSObject.Properties.Name.Contains("dispatcher_pid")) {
|
|
103
|
+
$currentRuntime.dispatcher_pid = $dispatcherPid
|
|
104
|
+
} else {
|
|
105
|
+
$currentRuntime | Add-Member -NotePropertyName "dispatcher_pid" -NotePropertyValue $dispatcherPid
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if ($responderPid) {
|
|
109
|
+
if ($currentRuntime.PSObject.Properties.Name.Contains("responder_pid")) {
|
|
110
|
+
$currentRuntime.responder_pid = $responderPid
|
|
111
|
+
} else {
|
|
112
|
+
$currentRuntime | Add-Member -NotePropertyName "responder_pid" -NotePropertyValue $responderPid
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if ($envFile["BLUN_TELEGRAM_APP_SERVER_WS_URL"] -and $telegramPluginRoot) {
|
|
118
|
+
try {
|
|
119
|
+
$bootstrapScript = Join-Path $telegramPluginRoot "app-server-cli.js"
|
|
120
|
+
$loaded = & node $bootstrapScript "list-loaded" "--ws-url" $envFile["BLUN_TELEGRAM_APP_SERVER_WS_URL"] | ConvertFrom-Json
|
|
121
|
+
$loadedThreads = @($loaded.data)
|
|
122
|
+
} catch {
|
|
123
|
+
$loadedThreads = @()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
$result = [ordered]@{
|
|
128
|
+
profile = $profileAgent
|
|
129
|
+
state_dir = $stateDir
|
|
130
|
+
plugin_root = $telegramPluginRoot
|
|
131
|
+
active_ws = $envFile["BLUN_TELEGRAM_APP_SERVER_WS_URL"]
|
|
132
|
+
env_thread_id = $envFile["BLUN_TELEGRAM_THREAD_ID"]
|
|
133
|
+
state_thread_id = $stateThreadId
|
|
134
|
+
active_thread_id = if ($stateThreadId) { $stateThreadId } else { $envFile["BLUN_TELEGRAM_THREAD_ID"] }
|
|
135
|
+
current_runtime = $currentRuntime
|
|
136
|
+
loaded_threads = $loadedThreads
|
|
137
|
+
queue_depth = $queued.Count
|
|
138
|
+
submitted_depth = $submitted.Count
|
|
139
|
+
pending_reply_depth = $pendingReplies.Count
|
|
140
|
+
delivered_count = $delivered.Count
|
|
141
|
+
error_count = $errors.Count
|
|
142
|
+
history_count = $queue.Count
|
|
143
|
+
last_inbound = $state.lastInbound
|
|
144
|
+
last_outbound = $state.lastOutbound
|
|
145
|
+
poller_pid = $pollerPid
|
|
146
|
+
dispatcher_pid = $dispatcherPid
|
|
147
|
+
responder_pid = $responderPid
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ($result.poller_pid) {
|
|
151
|
+
$result["poller_alive"] = Test-PidAlive -ProcId ([int]$result.poller_pid)
|
|
152
|
+
}
|
|
153
|
+
if ($result.dispatcher_pid) {
|
|
154
|
+
$result["dispatcher_alive"] = Test-PidAlive -ProcId ([int]$result.dispatcher_pid)
|
|
155
|
+
}
|
|
156
|
+
if ($result.responder_pid) {
|
|
157
|
+
$result["responder_alive"] = Test-PidAlive -ProcId ([int]$result.responder_pid)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
$result | ConvertTo-Json -Depth 8
|