@blunking/codexlink 0.1.12 → 0.1.14

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,532 +1,532 @@
1
- param(
2
- [Parameter(Mandatory = $true)]
3
- [string]$Agent,
4
-
5
- [string]$Prompt = "",
6
- [string]$Workspace = "",
7
- [ValidateSet("inherit", "plugin", "off")]
8
- [string]$TelegramMode = "inherit",
9
- [switch]$PrintOnly,
10
- [switch]$RemoteControl,
11
- [string[]]$ExtraArgs = @()
12
- )
13
-
14
- $ErrorActionPreference = "Stop"
15
-
16
- function Get-JsonFile {
17
- param([string]$Path)
18
- if (-not (Test-Path $Path)) {
19
- throw "Profile not found: $Path"
20
- }
21
- return Get-Content -Raw -Path $Path | ConvertFrom-Json
22
- }
23
-
24
- function Try-GetJsonFile {
25
- param([string]$Path)
26
- if (-not (Test-Path $Path)) {
27
- return $null
28
- }
29
- try {
30
- return Get-Content -Raw -Path $Path | ConvertFrom-Json
31
- } catch {
32
- return $null
33
- }
34
- }
35
-
36
- function Get-ProfilePath {
37
- param(
38
- [string]$RuntimeRoot,
39
- [string]$ProfileName
40
- )
41
-
42
- $normalized = [string]$ProfileName
43
- if (-not $normalized) { $normalized = "" }
44
- $normalized = $normalized.ToLower()
45
- $candidates = @()
46
- if ($env:BLUN_CODEX_PROFILE_ROOT) {
47
- $candidates += (Join-Path $env:BLUN_CODEX_PROFILE_ROOT ($normalized + ".json"))
48
- }
49
- $candidates += (Join-Path $env:USERPROFILE (".codex\\profiles\\codexlink\\" + $normalized + ".json"))
50
- $candidates += (Join-Path $RuntimeRoot ("profiles\\" + $normalized + ".json"))
51
-
52
- foreach ($candidate in $candidates) {
53
- if ($candidate -and (Test-Path $candidate)) {
54
- return $candidate
55
- }
56
- }
57
-
58
- return $candidates[-1]
59
- }
60
-
61
- function Ensure-Dir {
62
- param([string]$Path)
63
- if (-not (Test-Path $Path)) {
64
- New-Item -ItemType Directory -Path $Path -Force | Out-Null
65
- }
66
- }
67
-
68
- function Set-EnvVar {
69
- param([string]$Name, [string]$Value)
70
- if ($null -ne $Value -and $Value -ne "") {
71
- [Environment]::SetEnvironmentVariable($Name, $Value, "Process")
72
- }
73
- }
74
-
75
- function Resolve-ConfiguredPath {
76
- param([string]$Value)
77
- if (-not $Value) {
78
- return ""
79
- }
80
- $expanded = [Environment]::ExpandEnvironmentVariables($Value)
81
- if ([System.IO.Path]::IsPathRooted($expanded)) {
82
- return $expanded
83
- }
84
- return [System.IO.Path]::GetFullPath((Join-Path $runtimeRoot $expanded))
85
- }
86
-
87
- function Get-TelegramPluginRoot {
88
- param([string]$RuntimeRoot)
89
-
90
- $candidates = @()
91
- if ($env:BLUN_CODEX_TELEGRAM_PLUGIN_ROOT) {
92
- $candidates += $env:BLUN_CODEX_TELEGRAM_PLUGIN_ROOT
93
- }
94
- $candidates += (Join-Path $RuntimeRoot "telegram-plugin")
95
-
96
- foreach ($candidate in $candidates) {
97
- if (-not $candidate) { continue }
98
- if ((Test-Path (Join-Path $candidate "app-server-cli.js")) -and (Test-Path (Join-Path $candidate "sidecar-manager.js"))) {
99
- return $candidate
100
- }
101
- }
102
-
103
- throw "Unable to locate the Telegram plugin root. Set BLUN_CODEX_TELEGRAM_PLUGIN_ROOT or include telegram-plugin beside the runtime."
104
- }
105
-
106
- function Get-FreeTcpPort {
107
- $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
108
- $listener.Start()
109
- try {
110
- return $listener.LocalEndpoint.Port
111
- } finally {
112
- $listener.Stop()
113
- }
114
- }
115
-
1
+ param(
2
+ [Parameter(Mandatory = $true)]
3
+ [string]$Agent,
4
+
5
+ [string]$Prompt = "",
6
+ [string]$Workspace = "",
7
+ [ValidateSet("inherit", "plugin", "off")]
8
+ [string]$TelegramMode = "inherit",
9
+ [switch]$PrintOnly,
10
+ [switch]$RemoteControl,
11
+ [string[]]$ExtraArgs = @()
12
+ )
13
+
14
+ $ErrorActionPreference = "Stop"
15
+
16
+ function Get-JsonFile {
17
+ param([string]$Path)
18
+ if (-not (Test-Path $Path)) {
19
+ throw "Profile not found: $Path"
20
+ }
21
+ return Get-Content -Raw -Path $Path | ConvertFrom-Json
22
+ }
23
+
24
+ function Try-GetJsonFile {
25
+ param([string]$Path)
26
+ if (-not (Test-Path $Path)) {
27
+ return $null
28
+ }
29
+ try {
30
+ return Get-Content -Raw -Path $Path | ConvertFrom-Json
31
+ } catch {
32
+ return $null
33
+ }
34
+ }
35
+
36
+ function Get-ProfilePath {
37
+ param(
38
+ [string]$RuntimeRoot,
39
+ [string]$ProfileName
40
+ )
41
+
42
+ $normalized = [string]$ProfileName
43
+ if (-not $normalized) { $normalized = "" }
44
+ $normalized = $normalized.ToLower()
45
+ $candidates = @()
46
+ if ($env:BLUN_CODEX_PROFILE_ROOT) {
47
+ $candidates += (Join-Path $env:BLUN_CODEX_PROFILE_ROOT ($normalized + ".json"))
48
+ }
49
+ $candidates += (Join-Path $env:USERPROFILE (".codex\\profiles\\codexlink\\" + $normalized + ".json"))
50
+ $candidates += (Join-Path $RuntimeRoot ("profiles\\" + $normalized + ".json"))
51
+
52
+ foreach ($candidate in $candidates) {
53
+ if ($candidate -and (Test-Path $candidate)) {
54
+ return $candidate
55
+ }
56
+ }
57
+
58
+ return $candidates[-1]
59
+ }
60
+
61
+ function Ensure-Dir {
62
+ param([string]$Path)
63
+ if (-not (Test-Path $Path)) {
64
+ New-Item -ItemType Directory -Path $Path -Force | Out-Null
65
+ }
66
+ }
67
+
68
+ function Set-EnvVar {
69
+ param([string]$Name, [string]$Value)
70
+ if ($null -ne $Value -and $Value -ne "") {
71
+ [Environment]::SetEnvironmentVariable($Name, $Value, "Process")
72
+ }
73
+ }
74
+
75
+ function Resolve-ConfiguredPath {
76
+ param([string]$Value)
77
+ if (-not $Value) {
78
+ return ""
79
+ }
80
+ $expanded = [Environment]::ExpandEnvironmentVariables($Value)
81
+ if ([System.IO.Path]::IsPathRooted($expanded)) {
82
+ return $expanded
83
+ }
84
+ return [System.IO.Path]::GetFullPath((Join-Path $runtimeRoot $expanded))
85
+ }
86
+
87
+ function Get-TelegramPluginRoot {
88
+ param([string]$RuntimeRoot)
89
+
90
+ $candidates = @()
91
+ if ($env:BLUN_CODEX_TELEGRAM_PLUGIN_ROOT) {
92
+ $candidates += $env:BLUN_CODEX_TELEGRAM_PLUGIN_ROOT
93
+ }
94
+ $candidates += (Join-Path $RuntimeRoot "telegram-plugin")
95
+
96
+ foreach ($candidate in $candidates) {
97
+ if (-not $candidate) { continue }
98
+ if ((Test-Path (Join-Path $candidate "app-server-cli.js")) -and (Test-Path (Join-Path $candidate "sidecar-manager.js"))) {
99
+ return $candidate
100
+ }
101
+ }
102
+
103
+ throw "Unable to locate the Telegram plugin root. Set BLUN_CODEX_TELEGRAM_PLUGIN_ROOT or include telegram-plugin beside the runtime."
104
+ }
105
+
106
+ function Get-FreeTcpPort {
107
+ $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
108
+ $listener.Start()
109
+ try {
110
+ return $listener.LocalEndpoint.Port
111
+ } finally {
112
+ $listener.Stop()
113
+ }
114
+ }
115
+
116
116
  function Stop-ProcessTree {
117
- param([int[]]$RootIds)
118
-
119
- $all = @(Get-CimInstance Win32_Process)
120
- $targets = New-Object System.Collections.Generic.HashSet[int]
121
- $queue = New-Object System.Collections.Generic.Queue[int]
122
-
123
- foreach ($rootId in $RootIds) {
124
- if ($rootId -gt 0 -and $targets.Add($rootId)) {
125
- $queue.Enqueue($rootId)
126
- }
127
- }
128
-
129
- while ($queue.Count -gt 0) {
130
- $current = $queue.Dequeue()
131
- foreach ($proc in $all) {
132
- if ($proc.ParentProcessId -eq $current) {
133
- if ($targets.Add([int]$proc.ProcessId)) {
134
- $queue.Enqueue([int]$proc.ProcessId)
135
- }
136
- }
137
- }
138
- }
139
-
140
- $ordered = @($targets) | Sort-Object -Descending
141
- foreach ($procId in $ordered) {
142
- try {
143
- Stop-Process -Id $procId -Force -ErrorAction Stop
144
- } catch {
145
- }
146
- }
117
+ param([int[]]$RootIds)
118
+
119
+ $all = @(Get-CimInstance Win32_Process)
120
+ $targets = New-Object System.Collections.Generic.HashSet[int]
121
+ $queue = New-Object System.Collections.Generic.Queue[int]
122
+
123
+ foreach ($rootId in $RootIds) {
124
+ if ($rootId -gt 0 -and $targets.Add($rootId)) {
125
+ $queue.Enqueue($rootId)
126
+ }
127
+ }
128
+
129
+ while ($queue.Count -gt 0) {
130
+ $current = $queue.Dequeue()
131
+ foreach ($proc in $all) {
132
+ if ($proc.ParentProcessId -eq $current) {
133
+ if ($targets.Add([int]$proc.ProcessId)) {
134
+ $queue.Enqueue([int]$proc.ProcessId)
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ $ordered = @($targets) | Sort-Object -Descending
141
+ foreach ($procId in $ordered) {
142
+ try {
143
+ Stop-Process -Id $procId -Force -ErrorAction Stop
144
+ } catch {
145
+ }
146
+ }
147
147
  }
148
148
 
149
149
  function Wait-TcpPort {
150
- param(
151
- [string]$HostName,
152
- [int]$Port,
153
- [int]$TimeoutMs = 15000
154
- )
155
-
156
- $deadline = [DateTime]::UtcNow.AddMilliseconds($TimeoutMs)
157
- while ([DateTime]::UtcNow -lt $deadline) {
158
- try {
159
- $client = [System.Net.Sockets.TcpClient]::new()
160
- $task = $client.ConnectAsync($HostName, $Port)
161
- $connected = $task.Wait(350)
162
- if ($connected -and $client.Connected) {
163
- $client.Dispose()
164
- return $true
165
- }
166
- $client.Dispose()
167
- } catch {
168
- }
169
- Start-Sleep -Milliseconds 200
170
- }
171
- return $false
172
- }
173
-
174
- function Read-DotEnvFile {
175
- param([string]$Path)
176
- $values = @{}
177
- if (-not (Test-Path $Path)) {
178
- return $values
179
- }
180
- foreach ($line in (Get-Content -Path $Path)) {
181
- if (-not $line) { continue }
182
- if ($line.Trim().StartsWith("#")) { continue }
183
- $parts = $line -split "=", 2
184
- if ($parts.Count -ne 2) { continue }
185
- $values[$parts[0].Trim()] = $parts[1]
186
- }
187
- return $values
188
- }
189
-
190
- function Write-DotEnvFile {
191
- param(
192
- [string]$Path,
193
- [hashtable]$Values
194
- )
195
-
196
- $lines = foreach ($key in ($Values.Keys | Sort-Object)) {
197
- "$key=$($Values[$key])"
198
- }
199
- Set-Content -Path $Path -Value $lines -Encoding UTF8
200
- }
201
-
202
- function Write-TextFileWithRetry {
203
- param(
204
- [string]$Path,
205
- [string]$Content,
206
- [int]$Attempts = 6,
207
- [int]$DelayMs = 120
208
- )
209
- for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
210
- try {
211
- Set-Content -Path $Path -Value $Content -Encoding UTF8
212
- return
213
- } catch {
214
- if ($attempt -eq $Attempts) { throw }
215
- Start-Sleep -Milliseconds $DelayMs
216
- }
217
- }
218
- }
219
-
220
- function Append-TextFileWithRetry {
221
- param(
222
- [string]$Path,
223
- [string]$Content,
224
- [int]$Attempts = 6,
225
- [int]$DelayMs = 120
226
- )
227
- for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
228
- try {
229
- Add-Content -Path $Path -Value $Content -Encoding UTF8
230
- return
231
- } catch {
232
- if ($attempt -eq $Attempts) { throw }
233
- Start-Sleep -Milliseconds $DelayMs
234
- }
235
- }
236
- }
237
-
238
- function Read-PidFileValue {
239
- param([string]$Path)
240
- if (-not (Test-Path $Path)) {
241
- return 0
242
- }
243
- try {
244
- return [int]((Get-Content -Raw -Path $Path).Trim())
245
- } catch {
246
- return 0
247
- }
248
- }
249
-
250
- function Write-DebugStage {
251
- param(
252
- [string]$Path,
253
- [string]$Message
254
- )
255
- $line = ((Get-Date).ToUniversalTime().ToString("o")) + " " + $Message
256
- Append-TextFileWithRetry -Path $Path -Content ($line + "`n")
257
- }
258
-
259
- function Invoke-NodeJsonWithRetry {
260
- param(
261
- [string[]]$NodeArgs,
262
- [int]$Attempts = 12,
263
- [int]$DelayMs = 500
264
- )
265
-
266
- $lastRaw = $null
267
- for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
268
- $stdoutPath = Join-Path $env:TEMP ("blun-codex-node-" + [guid]::NewGuid().ToString() + ".stdout.log")
269
- $stderrPath = Join-Path $env:TEMP ("blun-codex-node-" + [guid]::NewGuid().ToString() + ".stderr.log")
270
- $argLine = ($NodeArgs | ForEach-Object { '"' + ($_ -replace '"', '\"') + '"' }) -join " "
271
- $proc = Start-Process -FilePath "node" -ArgumentList $argLine -WindowStyle Hidden -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath -Wait -PassThru
272
- $stdoutText = if (Test-Path $stdoutPath) { Get-Content -Raw $stdoutPath } else { "" }
273
- $stderrText = if (Test-Path $stderrPath) { Get-Content -Raw $stderrPath } else { "" }
274
- Remove-Item $stdoutPath,$stderrPath -Force -ErrorAction SilentlyContinue
275
- $lastRaw = ($stdoutText + $stderrText).Trim()
276
- if ($proc.ExitCode -eq 0 -and $stdoutText.Trim()) {
277
- return ($stdoutText | ConvertFrom-Json)
278
- }
279
- if ($attempt -lt $Attempts) {
280
- Start-Sleep -Milliseconds $DelayMs
281
- }
282
- }
283
-
284
- throw ("Node command failed after retries: " + ($NodeArgs -join " ") + "`n" + ($lastRaw | Out-String))
285
- }
286
-
287
- function Quote-TomlLiteral {
288
- param([string]$Value)
289
- if ($null -eq $Value) {
290
- return $null
291
- }
292
- return "'" + ($Value -replace "'", "''") + "'"
293
- }
294
-
295
- function Quote-PowerShellLiteral {
296
- param([string]$Value)
297
- if ($null -eq $Value) {
298
- return "''"
299
- }
300
- return "'" + ($Value -replace "'", "''") + "'"
301
- }
302
-
303
- $runtimeRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
304
- $profilePath = Get-ProfilePath -RuntimeRoot $runtimeRoot -ProfileName $Agent
305
- $profile = Get-JsonFile -Path $profilePath
306
-
307
- $resolvedWorkspace = if ($Workspace) { $Workspace } elseif ($profile.workspace) { $profile.workspace } else { (Get-Location).Path }
308
- $resolvedWorkspace = Resolve-ConfiguredPath -Value $resolvedWorkspace
309
- if (-not $resolvedWorkspace) {
310
- throw "No workspace configured for agent $Agent"
311
- }
312
-
313
- $agentRuntimeDir = Join-Path $env:USERPROFILE (".codex\runtimes\" + $profile.agent_name)
314
- Ensure-Dir -Path $agentRuntimeDir
315
- $debugLogPath = Join-Path $agentRuntimeDir "launch-debug.log"
316
- Write-DebugStage -Path $debugLogPath -Message ("START agent=" + $profile.agent_name + " telegram_mode=" + $TelegramMode + " print_only=" + [string]$PrintOnly + " remote_control=" + [string]$RemoteControl)
317
-
318
- $launchAt = (Get-Date).ToUniversalTime().ToString("o")
319
- $launchManifest = [ordered]@{
320
- agent_name = $profile.agent_name
321
- display_name = $profile.display_name
322
- workspace = $resolvedWorkspace
323
- model = $profile.model
324
- reasoning_effort = $profile.reasoning_effort
325
- sandbox = $profile.sandbox
326
- approval_policy = $profile.approval_policy
327
- remote_control = [bool]$RemoteControl
328
- prompt = $Prompt
329
- extra_args = @($ExtraArgs)
330
- launched_at = $launchAt
331
- profile_path = $profilePath
332
- }
333
-
334
- $lastLaunchPath = Join-Path $agentRuntimeDir "last-launch.json"
335
- $launchHistoryPath = Join-Path $agentRuntimeDir "launch-history.jsonl"
336
- Write-TextFileWithRetry -Path $lastLaunchPath -Content ($launchManifest | ConvertTo-Json -Depth 8)
337
- Append-TextFileWithRetry -Path $launchHistoryPath -Content (($launchManifest | ConvertTo-Json -Compress -Depth 8) + "`n")
338
-
339
- Set-EnvVar "BLUN_CODEX_AGENT" $profile.agent_name
340
- Set-EnvVar "BLUN_CODEX_DISPLAY_NAME" $profile.display_name
341
- Set-EnvVar "BLUN_CODEX_PROFILE_PATH" $profilePath
342
- Set-EnvVar "BLUN_CODEX_RUNTIME_DIR" $agentRuntimeDir
343
- Set-EnvVar "BLUN_CODEX_LANE" $profile.lane
344
- Set-EnvVar "BLUN_CODEX_MODEL" $profile.model
345
- Set-EnvVar "BLUN_CODEX_REASONING_EFFORT" $profile.reasoning_effort
346
- Set-EnvVar "BLUN_CODEX_PERSONALITY" $profile.personality
347
-
348
- $mnemoInherit = $false
349
- $mnemoCwdOverride = $null
350
- $mnemoEnvOverride = $null
351
-
352
- if ($null -ne $profile.mnemo -and $profile.mnemo.inherit -eq $true) {
353
- $mnemoInherit = $true
354
- }
355
-
356
- if (-not $mnemoInherit -and $null -ne $profile.mnemo) {
357
- $effectiveDefaultAgent = if ($profile.mnemo.default_agent) { $profile.mnemo.default_agent } else { $profile.agent_name }
358
- $effectiveAgent = if ($profile.mnemo.agent) { $profile.mnemo.agent } else { $profile.agent_name }
359
-
360
- Set-EnvVar "MNEMO_DEFAULT_AGENT" $effectiveDefaultAgent
361
- Set-EnvVar "MNEMO_AGENT" $effectiveAgent
362
- Set-EnvVar "MNEMO_DB" $profile.mnemo.db
363
- Set-EnvVar "MNEMO_TENANT_ROOT" $profile.mnemo.tenant_root
364
- Set-EnvVar "MNEMO_OWNER_NAME" $profile.mnemo.owner_name
365
- if ($null -ne $profile.mnemo.timezone_offset_hours) {
366
- Set-EnvVar "MNEMO_TZ_OFFSET_HOURS" ([string]$profile.mnemo.timezone_offset_hours)
367
- }
368
- if ($null -ne $profile.mnemo.local_agents) {
369
- Set-EnvVar "MNEMO_LOCAL_AGENTS" (($profile.mnemo.local_agents) -join ",")
370
- }
371
- Set-EnvVar "MNEMO_DEFAULT_SCOPE" $profile.mnemo.default_scope
372
- Set-EnvVar "MNEMO_SCOPE" $profile.mnemo.scope
373
- $mnemoCwdOverride = $profile.mnemo.cwd
374
-
375
- $mnemoEnvPairs = @()
376
- $mnemoEnvPairs += "MNEMO_DEFAULT_AGENT = $(Quote-TomlLiteral $effectiveDefaultAgent)"
377
- $mnemoEnvPairs += "MNEMO_AGENT = $(Quote-TomlLiteral $effectiveAgent)"
378
- if ($profile.mnemo.db) {
379
- $mnemoEnvPairs += "MNEMO_DB = $(Quote-TomlLiteral $profile.mnemo.db)"
380
- }
381
- if ($profile.mnemo.tenant_root) {
382
- $mnemoEnvPairs += "MNEMO_TENANT_ROOT = $(Quote-TomlLiteral $profile.mnemo.tenant_root)"
383
- }
384
- if ($profile.mnemo.owner_name) {
385
- $mnemoEnvPairs += "MNEMO_OWNER_NAME = $(Quote-TomlLiteral $profile.mnemo.owner_name)"
386
- }
387
- if ($null -ne $profile.mnemo.timezone_offset_hours) {
388
- $mnemoEnvPairs += "MNEMO_TZ_OFFSET_HOURS = $(Quote-TomlLiteral ([string]$profile.mnemo.timezone_offset_hours))"
389
- }
390
- if ($null -ne $profile.mnemo.local_agents -and $profile.mnemo.local_agents.Count -gt 0) {
391
- $mnemoEnvPairs += "MNEMO_LOCAL_AGENTS = $(Quote-TomlLiteral (($profile.mnemo.local_agents) -join ','))"
392
- }
393
- if ($profile.mnemo.default_scope) {
394
- $mnemoEnvPairs += "MNEMO_DEFAULT_SCOPE = $(Quote-TomlLiteral $profile.mnemo.default_scope)"
395
- }
396
- if ($profile.mnemo.scope) {
397
- $mnemoEnvPairs += "MNEMO_SCOPE = $(Quote-TomlLiteral $profile.mnemo.scope)"
398
- }
399
- if ($mnemoEnvPairs.Count -gt 0) {
400
- $mnemoEnvOverride = "mcp_servers.mnemo.env={" + ($mnemoEnvPairs -join ", ") + "}"
401
- }
402
- }
403
-
404
- $telegramEnvOverride = $null
405
- $telegramEnabled = $false
406
- $telegramStateDir = $null
407
- $telegramAllowedChatId = $null
408
- $telegramAppServerWsUrl = $null
409
-
410
- if ($null -ne $profile.telegram) {
411
- $telegramEnabled = ($profile.telegram.enabled -eq $true)
412
- $telegramStateDir = $profile.telegram.state_dir
413
- $telegramAllowedChatId = $profile.telegram.allowed_chat_id
414
- }
415
-
416
- if ($TelegramMode -eq "plugin") {
417
- $telegramEnabled = $true
418
- } elseif ($TelegramMode -eq "off") {
419
- $telegramEnabled = $false
420
- }
421
-
422
- if (-not $telegramStateDir) {
423
- $telegramStateDir = Join-Path $env:USERPROFILE (".codex\channels\telegram-" + $profile.agent_name)
424
- } else {
425
- $telegramStateDir = Resolve-ConfiguredPath -Value $telegramStateDir
426
- }
427
-
428
- $telegramExistingEnv = @{}
429
- if ($telegramStateDir) {
430
- Ensure-Dir -Path $telegramStateDir
431
- $telegramExistingEnv = Read-DotEnvFile -Path (Join-Path $telegramStateDir ".env")
432
- }
433
-
434
- if (-not $telegramAllowedChatId -and $telegramExistingEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]) {
435
- $telegramAllowedChatId = [string]$telegramExistingEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]
436
- }
437
-
438
- if ($telegramEnabled) {
439
- Set-EnvVar "BLUN_TELEGRAM_AGENT_NAME" $profile.agent_name
440
- Set-EnvVar "BLUN_TELEGRAM_STATE_DIR" $telegramStateDir
441
- Set-EnvVar "BLUN_TELEGRAM_ALLOWED_CHAT_ID" $telegramAllowedChatId
442
- Set-EnvVar "BLUN_TELEGRAM_PLUGIN_MODE" $TelegramMode
443
- $telegramEnvPairs = @()
444
- $telegramEnvPairs += "BLUN_TELEGRAM_AGENT_NAME = $(Quote-TomlLiteral $profile.agent_name)"
445
- $telegramEnvPairs += "BLUN_TELEGRAM_STATE_DIR = $(Quote-TomlLiteral $telegramStateDir)"
446
- $telegramEnvPairs += "BLUN_TELEGRAM_PLUGIN_MODE = $(Quote-TomlLiteral $TelegramMode)"
447
- if ($telegramAllowedChatId) {
448
- $telegramEnvPairs += "BLUN_TELEGRAM_ALLOWED_CHAT_ID = $(Quote-TomlLiteral $telegramAllowedChatId)"
449
- }
450
- $telegramEnvOverride = "mcp_servers.codexlink_telegram.env={" + ($telegramEnvPairs -join ", ") + "}"
451
- }
452
-
453
- $useRemoteAppServer = ($TelegramMode -eq "plugin")
454
-
455
- $mnemoOverrides = @()
456
- if ($mnemoCwdOverride) {
457
- $mnemoOverrides += "-c"
458
- $mnemoOverrides += ("mcp_servers.mnemo.cwd=" + (Quote-TomlLiteral $mnemoCwdOverride))
459
- }
460
-
461
- if ($mnemoEnvOverride) {
462
- $mnemoOverrides += "-c"
463
- $mnemoOverrides += $mnemoEnvOverride
464
- }
465
-
466
- $commonOverrides = @() + $mnemoOverrides
467
- if ($telegramEnvOverride) {
468
- $commonOverrides += "-c"
469
- $commonOverrides += $telegramEnvOverride
470
- }
471
-
472
- $codexArgs = @()
473
-
474
- if ($profile.reasoning_effort) {
475
- $codexArgs += "-c"
476
- $codexArgs += ("model_reasoning_effort=""" + $profile.reasoning_effort + """")
477
- }
478
-
479
- if ($profile.personality) {
480
- $codexArgs += "-c"
481
- $codexArgs += ("personality=""" + $profile.personality + """")
482
- }
483
-
484
- if ($profile.sandbox) {
485
- $codexArgs += "--sandbox"
486
- $codexArgs += $profile.sandbox
487
- }
488
-
489
- if ($profile.approval_policy) {
490
- $codexArgs += "-a"
491
- $codexArgs += $profile.approval_policy
492
- }
493
-
494
- if ($ExtraArgs.Count -gt 0) {
495
- $codexArgs += $ExtraArgs
496
- }
497
-
498
- $remoteSessionInfo = $null
499
- $backendArgs = @()
500
- $envFilePath = $null
501
- $appServerInfoFile = $null
502
- $bootstrapScript = $null
503
- $sidecarManager = $null
504
-
505
- if ($useRemoteAppServer) {
506
- Write-DebugStage -Path $debugLogPath -Message "REMOTE_MODE enabled"
507
- $telegramPluginRoot = Get-TelegramPluginRoot -RuntimeRoot $runtimeRoot
508
- $port = Get-FreeTcpPort
509
- $telegramAppServerWsUrl = "ws://127.0.0.1:$port"
510
- $backendArgs = @("app-server", "--listen", $telegramAppServerWsUrl)
511
- if ($PrintOnly) {
512
- Write-DebugStage -Path $debugLogPath -Message ("PRINT_ONLY remote ws_url=" + $telegramAppServerWsUrl)
513
- $remoteSessionInfo = [ordered]@{
514
- ws_url = $telegramAppServerWsUrl
515
- thread_id = "{{THREAD_ID}}"
516
- pid = 0
517
- started_at = $null
518
- preview = $true
519
- }
520
- $codexArgs += "--remote"
521
- $codexArgs += $telegramAppServerWsUrl
522
- } else {
523
- $envFilePath = Join-Path $telegramStateDir ".env"
524
- $stateEnv = Read-DotEnvFile -Path $envFilePath
525
- $stateEnv["BLUN_TELEGRAM_AGENT_NAME"] = $profile.agent_name
526
- $stateEnv["BLUN_TELEGRAM_STATE_DIR"] = $telegramStateDir
527
- if ($telegramAllowedChatId) {
528
- $stateEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"] = $telegramAllowedChatId
529
- }
150
+ param(
151
+ [string]$HostName,
152
+ [int]$Port,
153
+ [int]$TimeoutMs = 15000
154
+ )
155
+
156
+ $deadline = [DateTime]::UtcNow.AddMilliseconds($TimeoutMs)
157
+ while ([DateTime]::UtcNow -lt $deadline) {
158
+ try {
159
+ $client = [System.Net.Sockets.TcpClient]::new()
160
+ $task = $client.ConnectAsync($HostName, $Port)
161
+ $connected = $task.Wait(350)
162
+ if ($connected -and $client.Connected) {
163
+ $client.Dispose()
164
+ return $true
165
+ }
166
+ $client.Dispose()
167
+ } catch {
168
+ }
169
+ Start-Sleep -Milliseconds 200
170
+ }
171
+ return $false
172
+ }
173
+
174
+ function Read-DotEnvFile {
175
+ param([string]$Path)
176
+ $values = @{}
177
+ if (-not (Test-Path $Path)) {
178
+ return $values
179
+ }
180
+ foreach ($line in (Get-Content -Path $Path)) {
181
+ if (-not $line) { continue }
182
+ if ($line.Trim().StartsWith("#")) { continue }
183
+ $parts = $line -split "=", 2
184
+ if ($parts.Count -ne 2) { continue }
185
+ $values[$parts[0].Trim()] = $parts[1]
186
+ }
187
+ return $values
188
+ }
189
+
190
+ function Write-DotEnvFile {
191
+ param(
192
+ [string]$Path,
193
+ [hashtable]$Values
194
+ )
195
+
196
+ $lines = foreach ($key in ($Values.Keys | Sort-Object)) {
197
+ "$key=$($Values[$key])"
198
+ }
199
+ Set-Content -Path $Path -Value $lines -Encoding UTF8
200
+ }
201
+
202
+ function Write-TextFileWithRetry {
203
+ param(
204
+ [string]$Path,
205
+ [string]$Content,
206
+ [int]$Attempts = 6,
207
+ [int]$DelayMs = 120
208
+ )
209
+ for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
210
+ try {
211
+ Set-Content -Path $Path -Value $Content -Encoding UTF8
212
+ return
213
+ } catch {
214
+ if ($attempt -eq $Attempts) { throw }
215
+ Start-Sleep -Milliseconds $DelayMs
216
+ }
217
+ }
218
+ }
219
+
220
+ function Append-TextFileWithRetry {
221
+ param(
222
+ [string]$Path,
223
+ [string]$Content,
224
+ [int]$Attempts = 6,
225
+ [int]$DelayMs = 120
226
+ )
227
+ for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
228
+ try {
229
+ Add-Content -Path $Path -Value $Content -Encoding UTF8
230
+ return
231
+ } catch {
232
+ if ($attempt -eq $Attempts) { throw }
233
+ Start-Sleep -Milliseconds $DelayMs
234
+ }
235
+ }
236
+ }
237
+
238
+ function Read-PidFileValue {
239
+ param([string]$Path)
240
+ if (-not (Test-Path $Path)) {
241
+ return 0
242
+ }
243
+ try {
244
+ return [int]((Get-Content -Raw -Path $Path).Trim())
245
+ } catch {
246
+ return 0
247
+ }
248
+ }
249
+
250
+ function Write-DebugStage {
251
+ param(
252
+ [string]$Path,
253
+ [string]$Message
254
+ )
255
+ $line = ((Get-Date).ToUniversalTime().ToString("o")) + " " + $Message
256
+ Append-TextFileWithRetry -Path $Path -Content ($line + "`n")
257
+ }
258
+
259
+ function Invoke-NodeJsonWithRetry {
260
+ param(
261
+ [string[]]$NodeArgs,
262
+ [int]$Attempts = 12,
263
+ [int]$DelayMs = 500
264
+ )
265
+
266
+ $lastRaw = $null
267
+ for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
268
+ $stdoutPath = Join-Path $env:TEMP ("blun-codex-node-" + [guid]::NewGuid().ToString() + ".stdout.log")
269
+ $stderrPath = Join-Path $env:TEMP ("blun-codex-node-" + [guid]::NewGuid().ToString() + ".stderr.log")
270
+ $argLine = ($NodeArgs | ForEach-Object { '"' + ($_ -replace '"', '\"') + '"' }) -join " "
271
+ $proc = Start-Process -FilePath "node" -ArgumentList $argLine -WindowStyle Hidden -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath -Wait -PassThru
272
+ $stdoutText = if (Test-Path $stdoutPath) { Get-Content -Raw $stdoutPath } else { "" }
273
+ $stderrText = if (Test-Path $stderrPath) { Get-Content -Raw $stderrPath } else { "" }
274
+ Remove-Item $stdoutPath,$stderrPath -Force -ErrorAction SilentlyContinue
275
+ $lastRaw = ($stdoutText + $stderrText).Trim()
276
+ if ($proc.ExitCode -eq 0 -and $stdoutText.Trim()) {
277
+ return ($stdoutText | ConvertFrom-Json)
278
+ }
279
+ if ($attempt -lt $Attempts) {
280
+ Start-Sleep -Milliseconds $DelayMs
281
+ }
282
+ }
283
+
284
+ throw ("Node command failed after retries: " + ($NodeArgs -join " ") + "`n" + ($lastRaw | Out-String))
285
+ }
286
+
287
+ function Quote-TomlLiteral {
288
+ param([string]$Value)
289
+ if ($null -eq $Value) {
290
+ return $null
291
+ }
292
+ return "'" + ($Value -replace "'", "''") + "'"
293
+ }
294
+
295
+ function Quote-PowerShellLiteral {
296
+ param([string]$Value)
297
+ if ($null -eq $Value) {
298
+ return "''"
299
+ }
300
+ return "'" + ($Value -replace "'", "''") + "'"
301
+ }
302
+
303
+ $runtimeRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
304
+ $profilePath = Get-ProfilePath -RuntimeRoot $runtimeRoot -ProfileName $Agent
305
+ $profile = Get-JsonFile -Path $profilePath
306
+
307
+ $resolvedWorkspace = if ($Workspace) { $Workspace } elseif ($profile.workspace) { $profile.workspace } else { (Get-Location).Path }
308
+ $resolvedWorkspace = Resolve-ConfiguredPath -Value $resolvedWorkspace
309
+ if (-not $resolvedWorkspace) {
310
+ throw "No workspace configured for agent $Agent"
311
+ }
312
+
313
+ $agentRuntimeDir = Join-Path $env:USERPROFILE (".codex\runtimes\" + $profile.agent_name)
314
+ Ensure-Dir -Path $agentRuntimeDir
315
+ $debugLogPath = Join-Path $agentRuntimeDir "launch-debug.log"
316
+ Write-DebugStage -Path $debugLogPath -Message ("START agent=" + $profile.agent_name + " telegram_mode=" + $TelegramMode + " print_only=" + [string]$PrintOnly + " remote_control=" + [string]$RemoteControl)
317
+
318
+ $launchAt = (Get-Date).ToUniversalTime().ToString("o")
319
+ $launchManifest = [ordered]@{
320
+ agent_name = $profile.agent_name
321
+ display_name = $profile.display_name
322
+ workspace = $resolvedWorkspace
323
+ model = $profile.model
324
+ reasoning_effort = $profile.reasoning_effort
325
+ sandbox = $profile.sandbox
326
+ approval_policy = $profile.approval_policy
327
+ remote_control = [bool]$RemoteControl
328
+ prompt = $Prompt
329
+ extra_args = @($ExtraArgs)
330
+ launched_at = $launchAt
331
+ profile_path = $profilePath
332
+ }
333
+
334
+ $lastLaunchPath = Join-Path $agentRuntimeDir "last-launch.json"
335
+ $launchHistoryPath = Join-Path $agentRuntimeDir "launch-history.jsonl"
336
+ Write-TextFileWithRetry -Path $lastLaunchPath -Content ($launchManifest | ConvertTo-Json -Depth 8)
337
+ Append-TextFileWithRetry -Path $launchHistoryPath -Content (($launchManifest | ConvertTo-Json -Compress -Depth 8) + "`n")
338
+
339
+ Set-EnvVar "BLUN_CODEX_AGENT" $profile.agent_name
340
+ Set-EnvVar "BLUN_CODEX_DISPLAY_NAME" $profile.display_name
341
+ Set-EnvVar "BLUN_CODEX_PROFILE_PATH" $profilePath
342
+ Set-EnvVar "BLUN_CODEX_RUNTIME_DIR" $agentRuntimeDir
343
+ Set-EnvVar "BLUN_CODEX_LANE" $profile.lane
344
+ Set-EnvVar "BLUN_CODEX_MODEL" $profile.model
345
+ Set-EnvVar "BLUN_CODEX_REASONING_EFFORT" $profile.reasoning_effort
346
+ Set-EnvVar "BLUN_CODEX_PERSONALITY" $profile.personality
347
+
348
+ $mnemoInherit = $false
349
+ $mnemoCwdOverride = $null
350
+ $mnemoEnvOverride = $null
351
+
352
+ if ($null -ne $profile.mnemo -and $profile.mnemo.inherit -eq $true) {
353
+ $mnemoInherit = $true
354
+ }
355
+
356
+ if (-not $mnemoInherit -and $null -ne $profile.mnemo) {
357
+ $effectiveDefaultAgent = if ($profile.mnemo.default_agent) { $profile.mnemo.default_agent } else { $profile.agent_name }
358
+ $effectiveAgent = if ($profile.mnemo.agent) { $profile.mnemo.agent } else { $profile.agent_name }
359
+
360
+ Set-EnvVar "MNEMO_DEFAULT_AGENT" $effectiveDefaultAgent
361
+ Set-EnvVar "MNEMO_AGENT" $effectiveAgent
362
+ Set-EnvVar "MNEMO_DB" $profile.mnemo.db
363
+ Set-EnvVar "MNEMO_TENANT_ROOT" $profile.mnemo.tenant_root
364
+ Set-EnvVar "MNEMO_OWNER_NAME" $profile.mnemo.owner_name
365
+ if ($null -ne $profile.mnemo.timezone_offset_hours) {
366
+ Set-EnvVar "MNEMO_TZ_OFFSET_HOURS" ([string]$profile.mnemo.timezone_offset_hours)
367
+ }
368
+ if ($null -ne $profile.mnemo.local_agents) {
369
+ Set-EnvVar "MNEMO_LOCAL_AGENTS" (($profile.mnemo.local_agents) -join ",")
370
+ }
371
+ Set-EnvVar "MNEMO_DEFAULT_SCOPE" $profile.mnemo.default_scope
372
+ Set-EnvVar "MNEMO_SCOPE" $profile.mnemo.scope
373
+ $mnemoCwdOverride = $profile.mnemo.cwd
374
+
375
+ $mnemoEnvPairs = @()
376
+ $mnemoEnvPairs += "MNEMO_DEFAULT_AGENT = $(Quote-TomlLiteral $effectiveDefaultAgent)"
377
+ $mnemoEnvPairs += "MNEMO_AGENT = $(Quote-TomlLiteral $effectiveAgent)"
378
+ if ($profile.mnemo.db) {
379
+ $mnemoEnvPairs += "MNEMO_DB = $(Quote-TomlLiteral $profile.mnemo.db)"
380
+ }
381
+ if ($profile.mnemo.tenant_root) {
382
+ $mnemoEnvPairs += "MNEMO_TENANT_ROOT = $(Quote-TomlLiteral $profile.mnemo.tenant_root)"
383
+ }
384
+ if ($profile.mnemo.owner_name) {
385
+ $mnemoEnvPairs += "MNEMO_OWNER_NAME = $(Quote-TomlLiteral $profile.mnemo.owner_name)"
386
+ }
387
+ if ($null -ne $profile.mnemo.timezone_offset_hours) {
388
+ $mnemoEnvPairs += "MNEMO_TZ_OFFSET_HOURS = $(Quote-TomlLiteral ([string]$profile.mnemo.timezone_offset_hours))"
389
+ }
390
+ if ($null -ne $profile.mnemo.local_agents -and $profile.mnemo.local_agents.Count -gt 0) {
391
+ $mnemoEnvPairs += "MNEMO_LOCAL_AGENTS = $(Quote-TomlLiteral (($profile.mnemo.local_agents) -join ','))"
392
+ }
393
+ if ($profile.mnemo.default_scope) {
394
+ $mnemoEnvPairs += "MNEMO_DEFAULT_SCOPE = $(Quote-TomlLiteral $profile.mnemo.default_scope)"
395
+ }
396
+ if ($profile.mnemo.scope) {
397
+ $mnemoEnvPairs += "MNEMO_SCOPE = $(Quote-TomlLiteral $profile.mnemo.scope)"
398
+ }
399
+ if ($mnemoEnvPairs.Count -gt 0) {
400
+ $mnemoEnvOverride = "mcp_servers.mnemo.env={" + ($mnemoEnvPairs -join ", ") + "}"
401
+ }
402
+ }
403
+
404
+ $telegramEnvOverride = $null
405
+ $telegramEnabled = $false
406
+ $telegramStateDir = $null
407
+ $telegramAllowedChatId = $null
408
+ $telegramAppServerWsUrl = $null
409
+
410
+ if ($null -ne $profile.telegram) {
411
+ $telegramEnabled = ($profile.telegram.enabled -eq $true)
412
+ $telegramStateDir = $profile.telegram.state_dir
413
+ $telegramAllowedChatId = $profile.telegram.allowed_chat_id
414
+ }
415
+
416
+ if ($TelegramMode -eq "plugin") {
417
+ $telegramEnabled = $true
418
+ } elseif ($TelegramMode -eq "off") {
419
+ $telegramEnabled = $false
420
+ }
421
+
422
+ if (-not $telegramStateDir) {
423
+ $telegramStateDir = Join-Path $env:USERPROFILE (".codex\channels\telegram-" + $profile.agent_name)
424
+ } else {
425
+ $telegramStateDir = Resolve-ConfiguredPath -Value $telegramStateDir
426
+ }
427
+
428
+ $telegramExistingEnv = @{}
429
+ if ($telegramStateDir) {
430
+ Ensure-Dir -Path $telegramStateDir
431
+ $telegramExistingEnv = Read-DotEnvFile -Path (Join-Path $telegramStateDir ".env")
432
+ }
433
+
434
+ if (-not $telegramAllowedChatId -and $telegramExistingEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]) {
435
+ $telegramAllowedChatId = [string]$telegramExistingEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]
436
+ }
437
+
438
+ if ($telegramEnabled) {
439
+ Set-EnvVar "BLUN_TELEGRAM_AGENT_NAME" $profile.agent_name
440
+ Set-EnvVar "BLUN_TELEGRAM_STATE_DIR" $telegramStateDir
441
+ Set-EnvVar "BLUN_TELEGRAM_ALLOWED_CHAT_ID" $telegramAllowedChatId
442
+ Set-EnvVar "BLUN_TELEGRAM_PLUGIN_MODE" $TelegramMode
443
+ $telegramEnvPairs = @()
444
+ $telegramEnvPairs += "BLUN_TELEGRAM_AGENT_NAME = $(Quote-TomlLiteral $profile.agent_name)"
445
+ $telegramEnvPairs += "BLUN_TELEGRAM_STATE_DIR = $(Quote-TomlLiteral $telegramStateDir)"
446
+ $telegramEnvPairs += "BLUN_TELEGRAM_PLUGIN_MODE = $(Quote-TomlLiteral $TelegramMode)"
447
+ if ($telegramAllowedChatId) {
448
+ $telegramEnvPairs += "BLUN_TELEGRAM_ALLOWED_CHAT_ID = $(Quote-TomlLiteral $telegramAllowedChatId)"
449
+ }
450
+ $telegramEnvOverride = "mcp_servers.codexlink_telegram.env={" + ($telegramEnvPairs -join ", ") + "}"
451
+ }
452
+
453
+ $useRemoteAppServer = ($TelegramMode -eq "plugin")
454
+
455
+ $mnemoOverrides = @()
456
+ if ($mnemoCwdOverride) {
457
+ $mnemoOverrides += "-c"
458
+ $mnemoOverrides += ("mcp_servers.mnemo.cwd=" + (Quote-TomlLiteral $mnemoCwdOverride))
459
+ }
460
+
461
+ if ($mnemoEnvOverride) {
462
+ $mnemoOverrides += "-c"
463
+ $mnemoOverrides += $mnemoEnvOverride
464
+ }
465
+
466
+ $commonOverrides = @() + $mnemoOverrides
467
+ if ($telegramEnvOverride) {
468
+ $commonOverrides += "-c"
469
+ $commonOverrides += $telegramEnvOverride
470
+ }
471
+
472
+ $codexArgs = @()
473
+
474
+ if ($profile.reasoning_effort) {
475
+ $codexArgs += "-c"
476
+ $codexArgs += ("model_reasoning_effort=""" + $profile.reasoning_effort + """")
477
+ }
478
+
479
+ if ($profile.personality) {
480
+ $codexArgs += "-c"
481
+ $codexArgs += ("personality=""" + $profile.personality + """")
482
+ }
483
+
484
+ if ($profile.sandbox) {
485
+ $codexArgs += "--sandbox"
486
+ $codexArgs += $profile.sandbox
487
+ }
488
+
489
+ if ($profile.approval_policy) {
490
+ $codexArgs += "-a"
491
+ $codexArgs += $profile.approval_policy
492
+ }
493
+
494
+ if ($ExtraArgs.Count -gt 0) {
495
+ $codexArgs += $ExtraArgs
496
+ }
497
+
498
+ $remoteSessionInfo = $null
499
+ $backendArgs = @()
500
+ $envFilePath = $null
501
+ $appServerInfoFile = $null
502
+ $bootstrapScript = $null
503
+ $sidecarManager = $null
504
+
505
+ if ($useRemoteAppServer) {
506
+ Write-DebugStage -Path $debugLogPath -Message "REMOTE_MODE enabled"
507
+ $telegramPluginRoot = Get-TelegramPluginRoot -RuntimeRoot $runtimeRoot
508
+ $port = Get-FreeTcpPort
509
+ $telegramAppServerWsUrl = "ws://127.0.0.1:$port"
510
+ $backendArgs = @("app-server", "--listen", $telegramAppServerWsUrl)
511
+ if ($PrintOnly) {
512
+ Write-DebugStage -Path $debugLogPath -Message ("PRINT_ONLY remote ws_url=" + $telegramAppServerWsUrl)
513
+ $remoteSessionInfo = [ordered]@{
514
+ ws_url = $telegramAppServerWsUrl
515
+ thread_id = "{{THREAD_ID}}"
516
+ pid = 0
517
+ started_at = $null
518
+ preview = $true
519
+ }
520
+ $codexArgs += "--remote"
521
+ $codexArgs += $telegramAppServerWsUrl
522
+ } else {
523
+ $envFilePath = Join-Path $telegramStateDir ".env"
524
+ $stateEnv = Read-DotEnvFile -Path $envFilePath
525
+ $stateEnv["BLUN_TELEGRAM_AGENT_NAME"] = $profile.agent_name
526
+ $stateEnv["BLUN_TELEGRAM_STATE_DIR"] = $telegramStateDir
527
+ if ($telegramAllowedChatId) {
528
+ $stateEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"] = $telegramAllowedChatId
529
+ }
530
530
  $stateEnv["BLUN_TELEGRAM_PLUGIN_MODE"] = $TelegramMode
531
531
  $stateEnv["BLUN_TELEGRAM_APP_SERVER_WS_URL"] = $telegramAppServerWsUrl
532
532
  $stateEnv["BLUN_TELEGRAM_THREAD_ID"] = ""
@@ -545,139 +545,139 @@ if ($useRemoteAppServer) {
545
545
  }
546
546
 
547
547
  Set-EnvVar "BLUN_TELEGRAM_APP_SERVER_WS_URL" $telegramAppServerWsUrl
548
-
549
- $appServerPidFile = Join-Path $agentRuntimeDir "app-server.pid"
550
- $appServerInfoFile = Join-Path $agentRuntimeDir "app-server.json"
551
- $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
552
- $appServerStdoutPath = Join-Path $agentRuntimeDir "app-server.stdout.log"
553
- $appServerStderrPath = Join-Path $agentRuntimeDir "app-server.stderr.log"
554
- $previousRuntime = Try-GetJsonFile -Path $currentRuntimeFile
555
- if ($null -ne $previousRuntime) {
556
- $oldPids = @()
557
- if ($previousRuntime.frontend_host_pid) { $oldPids += [int]$previousRuntime.frontend_host_pid }
558
- if ($previousRuntime.app_server_pid) { $oldPids += [int]$previousRuntime.app_server_pid }
559
- if ($previousRuntime.queue_notifier_pid) { $oldPids += [int]$previousRuntime.queue_notifier_pid }
560
- if ($previousRuntime.poller_pid) { $oldPids += [int]$previousRuntime.poller_pid }
561
- if ($previousRuntime.dispatcher_pid) { $oldPids += [int]$previousRuntime.dispatcher_pid }
562
- if ($previousRuntime.responder_pid) { $oldPids += [int]$previousRuntime.responder_pid }
563
- if ($oldPids.Count -gt 0) {
564
- Stop-ProcessTree -RootIds $oldPids
565
- Write-DebugStage -Path $debugLogPath -Message ("PREVIOUS_RUNTIME_STOPPED pids=" + (($oldPids | Select-Object -Unique) -join ","))
566
- }
567
- Remove-Item $currentRuntimeFile -Force -ErrorAction SilentlyContinue
568
- }
569
- $codexScript = (Get-Command codex).Source
570
- $codexBaseDir = Split-Path -Parent $codexScript
571
- $codexJs = $null
572
- $vendorCandidates = Get-ChildItem -Path (Join-Path $codexBaseDir "node_modules\@*\codex\bin\codex.js") -File -ErrorAction SilentlyContinue
573
- if ($vendorCandidates) {
574
- $codexJs = $vendorCandidates[0].FullName
575
- } else {
576
- $codexJs = Join-Path $codexBaseDir "node_modules\codex\bin\codex.js"
577
- }
578
- $nodeExe = (Get-Command node).Source
579
- Remove-Item $appServerStdoutPath,$appServerStderrPath -Force -ErrorAction SilentlyContinue
580
- $clearedEnvNames = @(
581
- "BLUN_TELEGRAM_AGENT_NAME",
582
- "BLUN_TELEGRAM_STATE_DIR",
583
- "BLUN_TELEGRAM_ALLOWED_CHAT_ID",
584
- "BLUN_TELEGRAM_PLUGIN_MODE",
585
- "BLUN_TELEGRAM_APP_SERVER_WS_URL",
586
- "BLUN_TELEGRAM_THREAD_ID",
587
- "BLUN_TELEGRAM_BOT_TOKEN",
588
- "BLUN_TELEGRAM_CODEX_BIN"
589
- )
590
- $savedEnv = @{}
591
- foreach ($name in $clearedEnvNames) {
592
- $savedEnv[$name] = [Environment]::GetEnvironmentVariable($name, "Process")
593
- [Environment]::SetEnvironmentVariable($name, "", "Process")
594
- }
595
- try {
596
- $backendProcess = Start-Process -FilePath $nodeExe -ArgumentList @(
597
- $codexJs,
598
- "app-server",
599
- "--listen",
600
- $telegramAppServerWsUrl
601
- ) -WorkingDirectory $resolvedWorkspace -RedirectStandardOutput $appServerStdoutPath -RedirectStandardError $appServerStderrPath -PassThru -WindowStyle Hidden
602
- } finally {
603
- foreach ($name in $clearedEnvNames) {
604
- [Environment]::SetEnvironmentVariable($name, $savedEnv[$name], "Process")
605
- }
606
- }
607
- Set-Content -Path $appServerPidFile -Value "$($backendProcess.Id)`n" -Encoding UTF8
608
- Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_SPAWNED pid=" + $backendProcess.Id + " ws_url=" + $telegramAppServerWsUrl)
609
-
610
- if (-not (Wait-TcpPort -HostName "127.0.0.1" -Port $port -TimeoutMs 20000)) {
611
- $backendStderrTail = if (Test-Path $appServerStderrPath) { ((Get-Content -LiteralPath $appServerStderrPath -Tail 40) -join " | ") } else { "" }
612
- $backendStdoutTail = if (Test-Path $appServerStdoutPath) { ((Get-Content -LiteralPath $appServerStdoutPath -Tail 40) -join " | ") } else { "" }
613
- if ($backendProcess.HasExited) {
614
- Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_EXITED exit_code=" + $backendProcess.ExitCode)
615
- }
616
- if ($backendStderrTail) {
617
- Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_STDERR " + $backendStderrTail)
618
- }
619
- if ($backendStdoutTail) {
620
- Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_STDOUT " + $backendStdoutTail)
621
- }
622
- Write-DebugStage -Path $debugLogPath -Message "WAIT_TCP_TIMEOUT"
623
- throw "Timed out waiting for app-server to listen on $telegramAppServerWsUrl"
624
- }
625
- Write-DebugStage -Path $debugLogPath -Message "TCP_READY"
626
- $bootstrapScript = Join-Path $telegramPluginRoot "app-server-cli.js"
627
- $sidecarManager = Join-Path $telegramPluginRoot "sidecar-manager.js"
628
- $codexArgs += "--remote"
629
- $codexArgs += $telegramAppServerWsUrl
630
- }
631
- } elseif ($RemoteControl) {
632
- $codexArgs += "remote-control"
633
- } else {
634
- $codexArgs += "-C"
635
- $codexArgs += $resolvedWorkspace
636
- }
637
-
638
- if (-not $useRemoteAppServer) {
639
- $codexArgs = $commonOverrides + $codexArgs
640
- }
641
-
642
- if ($Prompt) {
643
- $codexArgs += $Prompt
644
- }
645
-
646
- if ($PrintOnly) {
647
- [pscustomobject]@{
648
- runtime_root = $runtimeRoot
649
- profile_path = $profilePath
650
- workspace = $resolvedWorkspace
651
- env = @{
652
- BLUN_CODEX_AGENT = $env:BLUN_CODEX_AGENT
653
- BLUN_CODEX_LANE = $env:BLUN_CODEX_LANE
654
- MNEMO_DEFAULT_AGENT = $env:MNEMO_DEFAULT_AGENT
655
- MNEMO_AGENT = $env:MNEMO_AGENT
656
- MNEMO_DB = $env:MNEMO_DB
657
- MNEMO_TENANT_ROOT = $env:MNEMO_TENANT_ROOT
658
- BLUN_TELEGRAM_AGENT_NAME = $env:BLUN_TELEGRAM_AGENT_NAME
659
- BLUN_TELEGRAM_STATE_DIR = $env:BLUN_TELEGRAM_STATE_DIR
660
- BLUN_TELEGRAM_ALLOWED_CHAT_ID = $env:BLUN_TELEGRAM_ALLOWED_CHAT_ID
661
- BLUN_TELEGRAM_APP_SERVER_WS_URL = $env:BLUN_TELEGRAM_APP_SERVER_WS_URL
662
- BLUN_TELEGRAM_THREAD_ID = $env:BLUN_TELEGRAM_THREAD_ID
663
- }
664
- mcp_overrides = @{
665
- mnemo_inherit = $mnemoInherit
666
- mnemo_cwd = $mnemoCwdOverride
667
- mnemo_env = $mnemoEnvOverride
668
- telegram_env = $telegramEnvOverride
669
- telegram_mode = $TelegramMode
670
- }
671
- remote_session = $remoteSessionInfo
672
- backend_command = if ($backendArgs.Count -gt 0) { @("codex") + $backendArgs } else { @() }
673
- command = @("codex") + $codexArgs
674
- } | ConvertTo-Json -Depth 8
675
- exit 0
676
- }
677
-
678
- if ($useRemoteAppServer) {
679
- $codexScript = (Get-Command codex).Source
680
- $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
548
+
549
+ $appServerPidFile = Join-Path $agentRuntimeDir "app-server.pid"
550
+ $appServerInfoFile = Join-Path $agentRuntimeDir "app-server.json"
551
+ $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
552
+ $appServerStdoutPath = Join-Path $agentRuntimeDir "app-server.stdout.log"
553
+ $appServerStderrPath = Join-Path $agentRuntimeDir "app-server.stderr.log"
554
+ $previousRuntime = Try-GetJsonFile -Path $currentRuntimeFile
555
+ if ($null -ne $previousRuntime) {
556
+ $oldPids = @()
557
+ if ($previousRuntime.frontend_host_pid) { $oldPids += [int]$previousRuntime.frontend_host_pid }
558
+ if ($previousRuntime.app_server_pid) { $oldPids += [int]$previousRuntime.app_server_pid }
559
+ if ($previousRuntime.queue_notifier_pid) { $oldPids += [int]$previousRuntime.queue_notifier_pid }
560
+ if ($previousRuntime.poller_pid) { $oldPids += [int]$previousRuntime.poller_pid }
561
+ if ($previousRuntime.dispatcher_pid) { $oldPids += [int]$previousRuntime.dispatcher_pid }
562
+ if ($previousRuntime.responder_pid) { $oldPids += [int]$previousRuntime.responder_pid }
563
+ if ($oldPids.Count -gt 0) {
564
+ Stop-ProcessTree -RootIds $oldPids
565
+ Write-DebugStage -Path $debugLogPath -Message ("PREVIOUS_RUNTIME_STOPPED pids=" + (($oldPids | Select-Object -Unique) -join ","))
566
+ }
567
+ Remove-Item $currentRuntimeFile -Force -ErrorAction SilentlyContinue
568
+ }
569
+ $codexScript = (Get-Command codex).Source
570
+ $codexBaseDir = Split-Path -Parent $codexScript
571
+ $codexJs = $null
572
+ $vendorCandidates = Get-ChildItem -Path (Join-Path $codexBaseDir "node_modules\@*\codex\bin\codex.js") -File -ErrorAction SilentlyContinue
573
+ if ($vendorCandidates) {
574
+ $codexJs = $vendorCandidates[0].FullName
575
+ } else {
576
+ $codexJs = Join-Path $codexBaseDir "node_modules\codex\bin\codex.js"
577
+ }
578
+ $nodeExe = (Get-Command node).Source
579
+ Remove-Item $appServerStdoutPath,$appServerStderrPath -Force -ErrorAction SilentlyContinue
580
+ $clearedEnvNames = @(
581
+ "BLUN_TELEGRAM_AGENT_NAME",
582
+ "BLUN_TELEGRAM_STATE_DIR",
583
+ "BLUN_TELEGRAM_ALLOWED_CHAT_ID",
584
+ "BLUN_TELEGRAM_PLUGIN_MODE",
585
+ "BLUN_TELEGRAM_APP_SERVER_WS_URL",
586
+ "BLUN_TELEGRAM_THREAD_ID",
587
+ "BLUN_TELEGRAM_BOT_TOKEN",
588
+ "BLUN_TELEGRAM_CODEX_BIN"
589
+ )
590
+ $savedEnv = @{}
591
+ foreach ($name in $clearedEnvNames) {
592
+ $savedEnv[$name] = [Environment]::GetEnvironmentVariable($name, "Process")
593
+ [Environment]::SetEnvironmentVariable($name, "", "Process")
594
+ }
595
+ try {
596
+ $backendProcess = Start-Process -FilePath $nodeExe -ArgumentList @(
597
+ $codexJs,
598
+ "app-server",
599
+ "--listen",
600
+ $telegramAppServerWsUrl
601
+ ) -WorkingDirectory $resolvedWorkspace -RedirectStandardOutput $appServerStdoutPath -RedirectStandardError $appServerStderrPath -PassThru -WindowStyle Hidden
602
+ } finally {
603
+ foreach ($name in $clearedEnvNames) {
604
+ [Environment]::SetEnvironmentVariable($name, $savedEnv[$name], "Process")
605
+ }
606
+ }
607
+ Set-Content -Path $appServerPidFile -Value "$($backendProcess.Id)`n" -Encoding UTF8
608
+ Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_SPAWNED pid=" + $backendProcess.Id + " ws_url=" + $telegramAppServerWsUrl)
609
+
610
+ if (-not (Wait-TcpPort -HostName "127.0.0.1" -Port $port -TimeoutMs 20000)) {
611
+ $backendStderrTail = if (Test-Path $appServerStderrPath) { ((Get-Content -LiteralPath $appServerStderrPath -Tail 40) -join " | ") } else { "" }
612
+ $backendStdoutTail = if (Test-Path $appServerStdoutPath) { ((Get-Content -LiteralPath $appServerStdoutPath -Tail 40) -join " | ") } else { "" }
613
+ if ($backendProcess.HasExited) {
614
+ Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_EXITED exit_code=" + $backendProcess.ExitCode)
615
+ }
616
+ if ($backendStderrTail) {
617
+ Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_STDERR " + $backendStderrTail)
618
+ }
619
+ if ($backendStdoutTail) {
620
+ Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_STDOUT " + $backendStdoutTail)
621
+ }
622
+ Write-DebugStage -Path $debugLogPath -Message "WAIT_TCP_TIMEOUT"
623
+ throw "Timed out waiting for app-server to listen on $telegramAppServerWsUrl"
624
+ }
625
+ Write-DebugStage -Path $debugLogPath -Message "TCP_READY"
626
+ $bootstrapScript = Join-Path $telegramPluginRoot "app-server-cli.js"
627
+ $sidecarManager = Join-Path $telegramPluginRoot "sidecar-manager.js"
628
+ $codexArgs += "--remote"
629
+ $codexArgs += $telegramAppServerWsUrl
630
+ }
631
+ } elseif ($RemoteControl) {
632
+ $codexArgs += "remote-control"
633
+ } else {
634
+ $codexArgs += "-C"
635
+ $codexArgs += $resolvedWorkspace
636
+ }
637
+
638
+ if (-not $useRemoteAppServer) {
639
+ $codexArgs = $commonOverrides + $codexArgs
640
+ }
641
+
642
+ if ($Prompt) {
643
+ $codexArgs += $Prompt
644
+ }
645
+
646
+ if ($PrintOnly) {
647
+ [pscustomobject]@{
648
+ runtime_root = $runtimeRoot
649
+ profile_path = $profilePath
650
+ workspace = $resolvedWorkspace
651
+ env = @{
652
+ BLUN_CODEX_AGENT = $env:BLUN_CODEX_AGENT
653
+ BLUN_CODEX_LANE = $env:BLUN_CODEX_LANE
654
+ MNEMO_DEFAULT_AGENT = $env:MNEMO_DEFAULT_AGENT
655
+ MNEMO_AGENT = $env:MNEMO_AGENT
656
+ MNEMO_DB = $env:MNEMO_DB
657
+ MNEMO_TENANT_ROOT = $env:MNEMO_TENANT_ROOT
658
+ BLUN_TELEGRAM_AGENT_NAME = $env:BLUN_TELEGRAM_AGENT_NAME
659
+ BLUN_TELEGRAM_STATE_DIR = $env:BLUN_TELEGRAM_STATE_DIR
660
+ BLUN_TELEGRAM_ALLOWED_CHAT_ID = $env:BLUN_TELEGRAM_ALLOWED_CHAT_ID
661
+ BLUN_TELEGRAM_APP_SERVER_WS_URL = $env:BLUN_TELEGRAM_APP_SERVER_WS_URL
662
+ BLUN_TELEGRAM_THREAD_ID = $env:BLUN_TELEGRAM_THREAD_ID
663
+ }
664
+ mcp_overrides = @{
665
+ mnemo_inherit = $mnemoInherit
666
+ mnemo_cwd = $mnemoCwdOverride
667
+ mnemo_env = $mnemoEnvOverride
668
+ telegram_env = $telegramEnvOverride
669
+ telegram_mode = $TelegramMode
670
+ }
671
+ remote_session = $remoteSessionInfo
672
+ backend_command = if ($backendArgs.Count -gt 0) { @("codex") + $backendArgs } else { @() }
673
+ command = @("codex") + $codexArgs
674
+ } | ConvertTo-Json -Depth 8
675
+ exit 0
676
+ }
677
+
678
+ if ($useRemoteAppServer) {
679
+ $codexScript = (Get-Command codex).Source
680
+ $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
681
681
  $windowTitle = "BLUN Codex Telegram [" + $profile.agent_name + "] " + $telegramAppServerWsUrl
682
682
  $titleEmbedScript = Join-Path $runtimeRoot "telegram-title-embed.ps1"
683
683
  $titleEmbedLog = Join-Path $agentRuntimeDir "title-embed.log"
@@ -689,19 +689,19 @@ if ($useRemoteAppServer) {
689
689
  $resumeCommand += " -LogFile " + (Quote-PowerShellLiteral $titleEmbedLog)
690
690
  $resumeCommand += "; "
691
691
  }
692
- $resumeCommand += "$host.UI.RawUI.WindowTitle = " + (Quote-PowerShellLiteral $windowTitle) + "; & " + (Quote-PowerShellLiteral $codexScript) + " " + (($codexArgs | ForEach-Object { Quote-PowerShellLiteral $_ }) -join " ")
693
- Write-DebugStage -Path $debugLogPath -Message ("FRONTEND_SPAWN command=" + $resumeCommand)
694
- $frontendProcess = Start-Process -FilePath "powershell" -WorkingDirectory $resolvedWorkspace -ArgumentList @(
695
- "-NoExit",
696
- "-Command",
697
- $resumeCommand
698
- ) -PassThru
692
+ $resumeCommand += "$host.UI.RawUI.WindowTitle = " + (Quote-PowerShellLiteral $windowTitle) + "; & " + (Quote-PowerShellLiteral $codexScript) + " " + (($codexArgs | ForEach-Object { Quote-PowerShellLiteral $_ }) -join " ")
693
+ Write-DebugStage -Path $debugLogPath -Message ("FRONTEND_SPAWN command=" + $resumeCommand)
694
+ $frontendProcess = Start-Process -FilePath "powershell" -WorkingDirectory $resolvedWorkspace -ArgumentList @(
695
+ "-NoExit",
696
+ "-Command",
697
+ $resumeCommand
698
+ ) -PassThru
699
699
  $currentRuntime = [ordered]@{
700
700
  ws_url = $telegramAppServerWsUrl
701
701
  app_server_pid = $backendProcess.Id
702
702
  frontend_host_pid = $frontendProcess.Id
703
703
  profile = $profile.agent_name
704
- started_at = (Get-Date).ToUniversalTime().ToString("o")
704
+ started_at = (Get-Date).ToUniversalTime().ToString("o")
705
705
  }
706
706
  Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 4)
707
707
  Write-DebugStage -Path $debugLogPath -Message "FRONTEND_SPAWNED"
@@ -729,17 +729,17 @@ if ($useRemoteAppServer) {
729
729
  if ($envFilePath -and $bootstrapScript) {
730
730
  try {
731
731
  Write-DebugStage -Path $debugLogPath -Message "LOADED_THREAD_WAIT_START"
732
- $loadedIds = @()
733
- for ($attempt = 1; $attempt -le 40; $attempt++) {
734
- $loaded = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "list-loaded", "--ws-url", $telegramAppServerWsUrl) -Attempts 1 -DelayMs 0
735
- $loadedIds = @($loaded.data)
736
- if ($loadedIds.Count -gt 0) {
737
- Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_SEEN attempt=" + $attempt + " count=" + $loadedIds.Count)
738
- break
739
- }
740
- if ($attempt -lt 40) {
741
- Start-Sleep -Milliseconds 500
742
- }
732
+ $loadedIds = @()
733
+ for ($attempt = 1; $attempt -le 40; $attempt++) {
734
+ $loaded = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "list-loaded", "--ws-url", $telegramAppServerWsUrl) -Attempts 1 -DelayMs 0
735
+ $loadedIds = @($loaded.data)
736
+ if ($loadedIds.Count -gt 0) {
737
+ Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_SEEN attempt=" + $attempt + " count=" + $loadedIds.Count)
738
+ break
739
+ }
740
+ if ($attempt -lt 40) {
741
+ Start-Sleep -Milliseconds 500
742
+ }
743
743
  }
744
744
  if ($loadedIds.Count -gt 0) {
745
745
  $activeThreadId = [string]$loadedIds[$loadedIds.Count - 1]
@@ -801,47 +801,47 @@ if ($useRemoteAppServer) {
801
801
  }
802
802
 
803
803
  $currentRuntime["thread_id"] = $activeThreadId
804
- Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
805
- Write-DebugStage -Path $debugLogPath -Message "CURRENT_RUNTIME_THREAD_WRITTEN"
806
-
807
- try {
808
- $verify = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "read-thread", "--ws-url", $telegramAppServerWsUrl, "--thread-id", $activeThreadId) -Attempts 2 -DelayMs 350
809
- if ($verify.ok -and $verify.threadId -eq $activeThreadId) {
810
- Write-DebugStage -Path $debugLogPath -Message "VERIFY_THREAD_OK"
811
- } else {
812
- Write-DebugStage -Path $debugLogPath -Message "VERIFY_THREAD_INVALID"
813
- }
814
- } catch {
815
- Write-DebugStage -Path $debugLogPath -Message ("VERIFY_THREAD_WARN " + $_.Exception.Message)
816
- }
817
-
818
- & node $sidecarManager | Out-Null
819
- if ($LASTEXITCODE -ne 0) {
820
- Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_FAILED"
821
- throw "Failed to ensure Telegram sidecars."
822
- }
823
- Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_OK"
824
-
825
- $pollerPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "poller.pid")
826
- $dispatcherPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "dispatcher.pid")
827
- $responderPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "responder.pid")
828
- $remoteSessionInfo = [ordered]@{
829
- ws_url = $telegramAppServerWsUrl
830
- thread_id = $activeThreadId
831
- pid = $backendProcess.Id
832
- started_at = (Get-Date).ToUniversalTime().ToString("o")
833
- sidecars = [ordered]@{
834
- poller = [ordered]@{ pid = $pollerPid }
835
- dispatcher = [ordered]@{ pid = $dispatcherPid }
836
- responder = [ordered]@{ pid = $responderPid }
837
- }
838
- }
839
- Write-TextFileWithRetry -Path $appServerInfoFile -Content ($remoteSessionInfo | ConvertTo-Json -Depth 6)
840
- if ($pollerPid -gt 0) { $currentRuntime["poller_pid"] = $pollerPid }
841
- if ($dispatcherPid -gt 0) { $currentRuntime["dispatcher_pid"] = $dispatcherPid }
842
- if ($responderPid -gt 0) { $currentRuntime["responder_pid"] = $responderPid }
843
- Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
844
- Write-DebugStage -Path $debugLogPath -Message "APP_SERVER_INFO_WRITTEN"
804
+ Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
805
+ Write-DebugStage -Path $debugLogPath -Message "CURRENT_RUNTIME_THREAD_WRITTEN"
806
+
807
+ try {
808
+ $verify = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "read-thread", "--ws-url", $telegramAppServerWsUrl, "--thread-id", $activeThreadId) -Attempts 2 -DelayMs 350
809
+ if ($verify.ok -and $verify.threadId -eq $activeThreadId) {
810
+ Write-DebugStage -Path $debugLogPath -Message "VERIFY_THREAD_OK"
811
+ } else {
812
+ Write-DebugStage -Path $debugLogPath -Message "VERIFY_THREAD_INVALID"
813
+ }
814
+ } catch {
815
+ Write-DebugStage -Path $debugLogPath -Message ("VERIFY_THREAD_WARN " + $_.Exception.Message)
816
+ }
817
+
818
+ & node $sidecarManager | Out-Null
819
+ if ($LASTEXITCODE -ne 0) {
820
+ Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_FAILED"
821
+ throw "Failed to ensure Telegram sidecars."
822
+ }
823
+ Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_OK"
824
+
825
+ $pollerPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "poller.pid")
826
+ $dispatcherPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "dispatcher.pid")
827
+ $responderPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "responder.pid")
828
+ $remoteSessionInfo = [ordered]@{
829
+ ws_url = $telegramAppServerWsUrl
830
+ thread_id = $activeThreadId
831
+ pid = $backendProcess.Id
832
+ started_at = (Get-Date).ToUniversalTime().ToString("o")
833
+ sidecars = [ordered]@{
834
+ poller = [ordered]@{ pid = $pollerPid }
835
+ dispatcher = [ordered]@{ pid = $dispatcherPid }
836
+ responder = [ordered]@{ pid = $responderPid }
837
+ }
838
+ }
839
+ Write-TextFileWithRetry -Path $appServerInfoFile -Content ($remoteSessionInfo | ConvertTo-Json -Depth 6)
840
+ if ($pollerPid -gt 0) { $currentRuntime["poller_pid"] = $pollerPid }
841
+ if ($dispatcherPid -gt 0) { $currentRuntime["dispatcher_pid"] = $dispatcherPid }
842
+ if ($responderPid -gt 0) { $currentRuntime["responder_pid"] = $responderPid }
843
+ Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
844
+ Write-DebugStage -Path $debugLogPath -Message "APP_SERVER_INFO_WRITTEN"
845
845
  } else {
846
846
  Write-DebugStage -Path $debugLogPath -Message "LOADED_THREAD_NONE"
847
847
  if ($sidecarsStartedEarly) {
@@ -860,12 +860,12 @@ if ($useRemoteAppServer) {
860
860
  Write-DebugStage -Path $debugLogPath -Message "APP_SERVER_INFO_WRITTEN_UNBOUND"
861
861
  }
862
862
  }
863
- } catch {
864
- Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_ERROR " + $_.Exception.Message)
865
- }
866
- }
867
- exit 0
868
- }
869
-
870
- & codex @codexArgs
871
- exit $LASTEXITCODE
863
+ } catch {
864
+ Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_ERROR " + $_.Exception.Message)
865
+ }
866
+ }
867
+ exit 0
868
+ }
869
+
870
+ & codex @codexArgs
871
+ exit $LASTEXITCODE