@blunking/codexlink 0.1.19 → 0.1.20

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,474 +1,474 @@
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
-
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
472
  $codexArgs = @()
473
473
 
474
474
  if ($profile.model) {
@@ -480,52 +480,52 @@ if ($profile.reasoning_effort) {
480
480
  $codexArgs += "-c"
481
481
  $codexArgs += ("model_reasoning_effort=""" + $profile.reasoning_effort + """")
482
482
  }
483
-
484
- if ($profile.personality) {
485
- $codexArgs += "-c"
486
- $codexArgs += ("personality=""" + $profile.personality + """")
487
- }
488
-
489
- if ($profile.sandbox) {
490
- $codexArgs += "--sandbox"
491
- $codexArgs += $profile.sandbox
492
- }
493
-
494
- if ($profile.approval_policy) {
495
- $codexArgs += "-a"
496
- $codexArgs += $profile.approval_policy
497
- }
498
-
499
- if ($ExtraArgs.Count -gt 0) {
500
- $codexArgs += $ExtraArgs
501
- }
502
-
503
- $remoteSessionInfo = $null
504
- $backendArgs = @()
505
- $envFilePath = $null
506
- $appServerInfoFile = $null
507
- $bootstrapScript = $null
508
- $sidecarManager = $null
509
-
510
- if ($useRemoteAppServer) {
511
- Write-DebugStage -Path $debugLogPath -Message "REMOTE_MODE enabled"
512
- $telegramPluginRoot = Get-TelegramPluginRoot -RuntimeRoot $runtimeRoot
513
- $port = Get-FreeTcpPort
514
- $telegramAppServerWsUrl = "ws://127.0.0.1:$port"
515
- $backendArgs = @("app-server", "--listen", $telegramAppServerWsUrl)
516
- if ($PrintOnly) {
517
- Write-DebugStage -Path $debugLogPath -Message ("PRINT_ONLY remote ws_url=" + $telegramAppServerWsUrl)
518
- $remoteSessionInfo = [ordered]@{
519
- ws_url = $telegramAppServerWsUrl
520
- thread_id = "{{THREAD_ID}}"
521
- pid = 0
522
- started_at = $null
523
- preview = $true
524
- }
525
- $codexArgs += "--remote"
526
- $codexArgs += $telegramAppServerWsUrl
527
- } else {
528
- $envFilePath = Join-Path $telegramStateDir ".env"
483
+
484
+ if ($profile.personality) {
485
+ $codexArgs += "-c"
486
+ $codexArgs += ("personality=""" + $profile.personality + """")
487
+ }
488
+
489
+ if ($profile.sandbox) {
490
+ $codexArgs += "--sandbox"
491
+ $codexArgs += $profile.sandbox
492
+ }
493
+
494
+ if ($profile.approval_policy) {
495
+ $codexArgs += "-a"
496
+ $codexArgs += $profile.approval_policy
497
+ }
498
+
499
+ if ($ExtraArgs.Count -gt 0) {
500
+ $codexArgs += $ExtraArgs
501
+ }
502
+
503
+ $remoteSessionInfo = $null
504
+ $backendArgs = @()
505
+ $envFilePath = $null
506
+ $appServerInfoFile = $null
507
+ $bootstrapScript = $null
508
+ $sidecarManager = $null
509
+
510
+ if ($useRemoteAppServer) {
511
+ Write-DebugStage -Path $debugLogPath -Message "REMOTE_MODE enabled"
512
+ $telegramPluginRoot = Get-TelegramPluginRoot -RuntimeRoot $runtimeRoot
513
+ $port = Get-FreeTcpPort
514
+ $telegramAppServerWsUrl = "ws://127.0.0.1:$port"
515
+ $backendArgs = @("app-server", "--listen", $telegramAppServerWsUrl)
516
+ if ($PrintOnly) {
517
+ Write-DebugStage -Path $debugLogPath -Message ("PRINT_ONLY remote ws_url=" + $telegramAppServerWsUrl)
518
+ $remoteSessionInfo = [ordered]@{
519
+ ws_url = $telegramAppServerWsUrl
520
+ thread_id = "{{THREAD_ID}}"
521
+ pid = 0
522
+ started_at = $null
523
+ preview = $true
524
+ }
525
+ $codexArgs += "--remote"
526
+ $codexArgs += $telegramAppServerWsUrl
527
+ } else {
528
+ $envFilePath = Join-Path $telegramStateDir ".env"
529
529
  $stateEnv = Read-DotEnvFile -Path $envFilePath
530
530
  $stateEnv["BLUN_TELEGRAM_AGENT_NAME"] = $profile.agent_name
531
531
  $stateEnv["BLUN_TELEGRAM_STATE_DIR"] = $telegramStateDir
@@ -555,139 +555,139 @@ if ($useRemoteAppServer) {
555
555
  }
556
556
 
557
557
  Set-EnvVar "BLUN_TELEGRAM_APP_SERVER_WS_URL" $telegramAppServerWsUrl
558
-
559
- $appServerPidFile = Join-Path $agentRuntimeDir "app-server.pid"
560
- $appServerInfoFile = Join-Path $agentRuntimeDir "app-server.json"
561
- $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
562
- $appServerStdoutPath = Join-Path $agentRuntimeDir "app-server.stdout.log"
563
- $appServerStderrPath = Join-Path $agentRuntimeDir "app-server.stderr.log"
564
- $previousRuntime = Try-GetJsonFile -Path $currentRuntimeFile
565
- if ($null -ne $previousRuntime) {
566
- $oldPids = @()
567
- if ($previousRuntime.frontend_host_pid) { $oldPids += [int]$previousRuntime.frontend_host_pid }
568
- if ($previousRuntime.app_server_pid) { $oldPids += [int]$previousRuntime.app_server_pid }
569
- if ($previousRuntime.queue_notifier_pid) { $oldPids += [int]$previousRuntime.queue_notifier_pid }
570
- if ($previousRuntime.poller_pid) { $oldPids += [int]$previousRuntime.poller_pid }
571
- if ($previousRuntime.dispatcher_pid) { $oldPids += [int]$previousRuntime.dispatcher_pid }
572
- if ($previousRuntime.responder_pid) { $oldPids += [int]$previousRuntime.responder_pid }
573
- if ($oldPids.Count -gt 0) {
574
- Stop-ProcessTree -RootIds $oldPids
575
- Write-DebugStage -Path $debugLogPath -Message ("PREVIOUS_RUNTIME_STOPPED pids=" + (($oldPids | Select-Object -Unique) -join ","))
576
- }
577
- Remove-Item $currentRuntimeFile -Force -ErrorAction SilentlyContinue
578
- }
579
- $codexScript = (Get-Command codex).Source
580
- $codexBaseDir = Split-Path -Parent $codexScript
581
- $codexJs = $null
582
- $vendorCandidates = Get-ChildItem -Path (Join-Path $codexBaseDir "node_modules\@*\codex\bin\codex.js") -File -ErrorAction SilentlyContinue
583
- if ($vendorCandidates) {
584
- $codexJs = $vendorCandidates[0].FullName
585
- } else {
586
- $codexJs = Join-Path $codexBaseDir "node_modules\codex\bin\codex.js"
587
- }
588
- $nodeExe = (Get-Command node).Source
589
- Remove-Item $appServerStdoutPath,$appServerStderrPath -Force -ErrorAction SilentlyContinue
590
- $clearedEnvNames = @(
591
- "BLUN_TELEGRAM_AGENT_NAME",
592
- "BLUN_TELEGRAM_STATE_DIR",
593
- "BLUN_TELEGRAM_ALLOWED_CHAT_ID",
594
- "BLUN_TELEGRAM_PLUGIN_MODE",
595
- "BLUN_TELEGRAM_APP_SERVER_WS_URL",
596
- "BLUN_TELEGRAM_THREAD_ID",
597
- "BLUN_TELEGRAM_BOT_TOKEN",
598
- "BLUN_TELEGRAM_CODEX_BIN"
599
- )
600
- $savedEnv = @{}
601
- foreach ($name in $clearedEnvNames) {
602
- $savedEnv[$name] = [Environment]::GetEnvironmentVariable($name, "Process")
603
- [Environment]::SetEnvironmentVariable($name, "", "Process")
604
- }
605
- try {
606
- $backendProcess = Start-Process -FilePath $nodeExe -ArgumentList @(
607
- $codexJs,
608
- "app-server",
609
- "--listen",
610
- $telegramAppServerWsUrl
611
- ) -WorkingDirectory $resolvedWorkspace -RedirectStandardOutput $appServerStdoutPath -RedirectStandardError $appServerStderrPath -PassThru -WindowStyle Hidden
612
- } finally {
613
- foreach ($name in $clearedEnvNames) {
614
- [Environment]::SetEnvironmentVariable($name, $savedEnv[$name], "Process")
615
- }
616
- }
617
- Set-Content -Path $appServerPidFile -Value "$($backendProcess.Id)`n" -Encoding UTF8
618
- Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_SPAWNED pid=" + $backendProcess.Id + " ws_url=" + $telegramAppServerWsUrl)
619
-
620
- if (-not (Wait-TcpPort -HostName "127.0.0.1" -Port $port -TimeoutMs 20000)) {
621
- $backendStderrTail = if (Test-Path $appServerStderrPath) { ((Get-Content -LiteralPath $appServerStderrPath -Tail 40) -join " | ") } else { "" }
622
- $backendStdoutTail = if (Test-Path $appServerStdoutPath) { ((Get-Content -LiteralPath $appServerStdoutPath -Tail 40) -join " | ") } else { "" }
623
- if ($backendProcess.HasExited) {
624
- Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_EXITED exit_code=" + $backendProcess.ExitCode)
625
- }
626
- if ($backendStderrTail) {
627
- Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_STDERR " + $backendStderrTail)
628
- }
629
- if ($backendStdoutTail) {
630
- Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_STDOUT " + $backendStdoutTail)
631
- }
632
- Write-DebugStage -Path $debugLogPath -Message "WAIT_TCP_TIMEOUT"
633
- throw "Timed out waiting for app-server to listen on $telegramAppServerWsUrl"
634
- }
635
- Write-DebugStage -Path $debugLogPath -Message "TCP_READY"
636
- $bootstrapScript = Join-Path $telegramPluginRoot "app-server-cli.js"
637
- $sidecarManager = Join-Path $telegramPluginRoot "sidecar-manager.js"
638
- $codexArgs += "--remote"
639
- $codexArgs += $telegramAppServerWsUrl
640
- }
641
- } elseif ($RemoteControl) {
642
- $codexArgs += "remote-control"
643
- } else {
644
- $codexArgs += "-C"
645
- $codexArgs += $resolvedWorkspace
646
- }
647
-
648
- if (-not $useRemoteAppServer) {
649
- $codexArgs = $commonOverrides + $codexArgs
650
- }
651
-
652
- if ($Prompt) {
653
- $codexArgs += $Prompt
654
- }
655
-
656
- if ($PrintOnly) {
657
- [pscustomobject]@{
658
- runtime_root = $runtimeRoot
659
- profile_path = $profilePath
660
- workspace = $resolvedWorkspace
661
- env = @{
662
- BLUN_CODEX_AGENT = $env:BLUN_CODEX_AGENT
663
- BLUN_CODEX_LANE = $env:BLUN_CODEX_LANE
664
- MNEMO_DEFAULT_AGENT = $env:MNEMO_DEFAULT_AGENT
665
- MNEMO_AGENT = $env:MNEMO_AGENT
666
- MNEMO_DB = $env:MNEMO_DB
667
- MNEMO_TENANT_ROOT = $env:MNEMO_TENANT_ROOT
668
- BLUN_TELEGRAM_AGENT_NAME = $env:BLUN_TELEGRAM_AGENT_NAME
669
- BLUN_TELEGRAM_STATE_DIR = $env:BLUN_TELEGRAM_STATE_DIR
670
- BLUN_TELEGRAM_ALLOWED_CHAT_ID = $env:BLUN_TELEGRAM_ALLOWED_CHAT_ID
671
- BLUN_TELEGRAM_APP_SERVER_WS_URL = $env:BLUN_TELEGRAM_APP_SERVER_WS_URL
672
- BLUN_TELEGRAM_THREAD_ID = $env:BLUN_TELEGRAM_THREAD_ID
673
- }
674
- mcp_overrides = @{
675
- mnemo_inherit = $mnemoInherit
676
- mnemo_cwd = $mnemoCwdOverride
677
- mnemo_env = $mnemoEnvOverride
678
- telegram_env = $telegramEnvOverride
679
- telegram_mode = $TelegramMode
680
- }
681
- remote_session = $remoteSessionInfo
682
- backend_command = if ($backendArgs.Count -gt 0) { @("codex") + $backendArgs } else { @() }
683
- command = @("codex") + $codexArgs
684
- } | ConvertTo-Json -Depth 8
685
- exit 0
686
- }
687
-
688
- if ($useRemoteAppServer) {
689
- $codexScript = (Get-Command codex).Source
690
- $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
558
+
559
+ $appServerPidFile = Join-Path $agentRuntimeDir "app-server.pid"
560
+ $appServerInfoFile = Join-Path $agentRuntimeDir "app-server.json"
561
+ $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
562
+ $appServerStdoutPath = Join-Path $agentRuntimeDir "app-server.stdout.log"
563
+ $appServerStderrPath = Join-Path $agentRuntimeDir "app-server.stderr.log"
564
+ $previousRuntime = Try-GetJsonFile -Path $currentRuntimeFile
565
+ if ($null -ne $previousRuntime) {
566
+ $oldPids = @()
567
+ if ($previousRuntime.frontend_host_pid) { $oldPids += [int]$previousRuntime.frontend_host_pid }
568
+ if ($previousRuntime.app_server_pid) { $oldPids += [int]$previousRuntime.app_server_pid }
569
+ if ($previousRuntime.queue_notifier_pid) { $oldPids += [int]$previousRuntime.queue_notifier_pid }
570
+ if ($previousRuntime.poller_pid) { $oldPids += [int]$previousRuntime.poller_pid }
571
+ if ($previousRuntime.dispatcher_pid) { $oldPids += [int]$previousRuntime.dispatcher_pid }
572
+ if ($previousRuntime.responder_pid) { $oldPids += [int]$previousRuntime.responder_pid }
573
+ if ($oldPids.Count -gt 0) {
574
+ Stop-ProcessTree -RootIds $oldPids
575
+ Write-DebugStage -Path $debugLogPath -Message ("PREVIOUS_RUNTIME_STOPPED pids=" + (($oldPids | Select-Object -Unique) -join ","))
576
+ }
577
+ Remove-Item $currentRuntimeFile -Force -ErrorAction SilentlyContinue
578
+ }
579
+ $codexScript = (Get-Command codex).Source
580
+ $codexBaseDir = Split-Path -Parent $codexScript
581
+ $codexJs = $null
582
+ $vendorCandidates = Get-ChildItem -Path (Join-Path $codexBaseDir "node_modules\@*\codex\bin\codex.js") -File -ErrorAction SilentlyContinue
583
+ if ($vendorCandidates) {
584
+ $codexJs = $vendorCandidates[0].FullName
585
+ } else {
586
+ $codexJs = Join-Path $codexBaseDir "node_modules\codex\bin\codex.js"
587
+ }
588
+ $nodeExe = (Get-Command node).Source
589
+ Remove-Item $appServerStdoutPath,$appServerStderrPath -Force -ErrorAction SilentlyContinue
590
+ $clearedEnvNames = @(
591
+ "BLUN_TELEGRAM_AGENT_NAME",
592
+ "BLUN_TELEGRAM_STATE_DIR",
593
+ "BLUN_TELEGRAM_ALLOWED_CHAT_ID",
594
+ "BLUN_TELEGRAM_PLUGIN_MODE",
595
+ "BLUN_TELEGRAM_APP_SERVER_WS_URL",
596
+ "BLUN_TELEGRAM_THREAD_ID",
597
+ "BLUN_TELEGRAM_BOT_TOKEN",
598
+ "BLUN_TELEGRAM_CODEX_BIN"
599
+ )
600
+ $savedEnv = @{}
601
+ foreach ($name in $clearedEnvNames) {
602
+ $savedEnv[$name] = [Environment]::GetEnvironmentVariable($name, "Process")
603
+ [Environment]::SetEnvironmentVariable($name, "", "Process")
604
+ }
605
+ try {
606
+ $backendProcess = Start-Process -FilePath $nodeExe -ArgumentList @(
607
+ $codexJs,
608
+ "app-server",
609
+ "--listen",
610
+ $telegramAppServerWsUrl
611
+ ) -WorkingDirectory $resolvedWorkspace -RedirectStandardOutput $appServerStdoutPath -RedirectStandardError $appServerStderrPath -PassThru -WindowStyle Hidden
612
+ } finally {
613
+ foreach ($name in $clearedEnvNames) {
614
+ [Environment]::SetEnvironmentVariable($name, $savedEnv[$name], "Process")
615
+ }
616
+ }
617
+ Set-Content -Path $appServerPidFile -Value "$($backendProcess.Id)`n" -Encoding UTF8
618
+ Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_SPAWNED pid=" + $backendProcess.Id + " ws_url=" + $telegramAppServerWsUrl)
619
+
620
+ if (-not (Wait-TcpPort -HostName "127.0.0.1" -Port $port -TimeoutMs 20000)) {
621
+ $backendStderrTail = if (Test-Path $appServerStderrPath) { ((Get-Content -LiteralPath $appServerStderrPath -Tail 40) -join " | ") } else { "" }
622
+ $backendStdoutTail = if (Test-Path $appServerStdoutPath) { ((Get-Content -LiteralPath $appServerStdoutPath -Tail 40) -join " | ") } else { "" }
623
+ if ($backendProcess.HasExited) {
624
+ Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_EXITED exit_code=" + $backendProcess.ExitCode)
625
+ }
626
+ if ($backendStderrTail) {
627
+ Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_STDERR " + $backendStderrTail)
628
+ }
629
+ if ($backendStdoutTail) {
630
+ Write-DebugStage -Path $debugLogPath -Message ("APP_SERVER_STDOUT " + $backendStdoutTail)
631
+ }
632
+ Write-DebugStage -Path $debugLogPath -Message "WAIT_TCP_TIMEOUT"
633
+ throw "Timed out waiting for app-server to listen on $telegramAppServerWsUrl"
634
+ }
635
+ Write-DebugStage -Path $debugLogPath -Message "TCP_READY"
636
+ $bootstrapScript = Join-Path $telegramPluginRoot "app-server-cli.js"
637
+ $sidecarManager = Join-Path $telegramPluginRoot "sidecar-manager.js"
638
+ $codexArgs += "--remote"
639
+ $codexArgs += $telegramAppServerWsUrl
640
+ }
641
+ } elseif ($RemoteControl) {
642
+ $codexArgs += "remote-control"
643
+ } else {
644
+ $codexArgs += "-C"
645
+ $codexArgs += $resolvedWorkspace
646
+ }
647
+
648
+ if (-not $useRemoteAppServer) {
649
+ $codexArgs = $commonOverrides + $codexArgs
650
+ }
651
+
652
+ if ($Prompt) {
653
+ $codexArgs += $Prompt
654
+ }
655
+
656
+ if ($PrintOnly) {
657
+ [pscustomobject]@{
658
+ runtime_root = $runtimeRoot
659
+ profile_path = $profilePath
660
+ workspace = $resolvedWorkspace
661
+ env = @{
662
+ BLUN_CODEX_AGENT = $env:BLUN_CODEX_AGENT
663
+ BLUN_CODEX_LANE = $env:BLUN_CODEX_LANE
664
+ MNEMO_DEFAULT_AGENT = $env:MNEMO_DEFAULT_AGENT
665
+ MNEMO_AGENT = $env:MNEMO_AGENT
666
+ MNEMO_DB = $env:MNEMO_DB
667
+ MNEMO_TENANT_ROOT = $env:MNEMO_TENANT_ROOT
668
+ BLUN_TELEGRAM_AGENT_NAME = $env:BLUN_TELEGRAM_AGENT_NAME
669
+ BLUN_TELEGRAM_STATE_DIR = $env:BLUN_TELEGRAM_STATE_DIR
670
+ BLUN_TELEGRAM_ALLOWED_CHAT_ID = $env:BLUN_TELEGRAM_ALLOWED_CHAT_ID
671
+ BLUN_TELEGRAM_APP_SERVER_WS_URL = $env:BLUN_TELEGRAM_APP_SERVER_WS_URL
672
+ BLUN_TELEGRAM_THREAD_ID = $env:BLUN_TELEGRAM_THREAD_ID
673
+ }
674
+ mcp_overrides = @{
675
+ mnemo_inherit = $mnemoInherit
676
+ mnemo_cwd = $mnemoCwdOverride
677
+ mnemo_env = $mnemoEnvOverride
678
+ telegram_env = $telegramEnvOverride
679
+ telegram_mode = $TelegramMode
680
+ }
681
+ remote_session = $remoteSessionInfo
682
+ backend_command = if ($backendArgs.Count -gt 0) { @("codex") + $backendArgs } else { @() }
683
+ command = @("codex") + $codexArgs
684
+ } | ConvertTo-Json -Depth 8
685
+ exit 0
686
+ }
687
+
688
+ if ($useRemoteAppServer) {
689
+ $codexScript = (Get-Command codex).Source
690
+ $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
691
691
  $windowTitle = "BLUN Codex Telegram [" + $profile.agent_name + "] " + $telegramAppServerWsUrl
692
692
  $titleEmbedScript = Join-Path $runtimeRoot "telegram-title-embed.ps1"
693
693
  $titleEmbedLog = Join-Path $agentRuntimeDir "title-embed.log"
@@ -699,19 +699,19 @@ if ($useRemoteAppServer) {
699
699
  $resumeCommand += " -LogFile " + (Quote-PowerShellLiteral $titleEmbedLog)
700
700
  $resumeCommand += "; "
701
701
  }
702
- $resumeCommand += "$host.UI.RawUI.WindowTitle = " + (Quote-PowerShellLiteral $windowTitle) + "; & " + (Quote-PowerShellLiteral $codexScript) + " " + (($codexArgs | ForEach-Object { Quote-PowerShellLiteral $_ }) -join " ")
703
- Write-DebugStage -Path $debugLogPath -Message ("FRONTEND_SPAWN command=" + $resumeCommand)
704
- $frontendProcess = Start-Process -FilePath "powershell" -WorkingDirectory $resolvedWorkspace -ArgumentList @(
705
- "-NoExit",
706
- "-Command",
707
- $resumeCommand
708
- ) -PassThru
702
+ $resumeCommand += "$host.UI.RawUI.WindowTitle = " + (Quote-PowerShellLiteral $windowTitle) + "; & " + (Quote-PowerShellLiteral $codexScript) + " " + (($codexArgs | ForEach-Object { Quote-PowerShellLiteral $_ }) -join " ")
703
+ Write-DebugStage -Path $debugLogPath -Message ("FRONTEND_SPAWN command=" + $resumeCommand)
704
+ $frontendProcess = Start-Process -FilePath "powershell" -WorkingDirectory $resolvedWorkspace -ArgumentList @(
705
+ "-NoExit",
706
+ "-Command",
707
+ $resumeCommand
708
+ ) -PassThru
709
709
  $currentRuntime = [ordered]@{
710
710
  ws_url = $telegramAppServerWsUrl
711
711
  app_server_pid = $backendProcess.Id
712
712
  frontend_host_pid = $frontendProcess.Id
713
713
  profile = $profile.agent_name
714
- started_at = (Get-Date).ToUniversalTime().ToString("o")
714
+ started_at = (Get-Date).ToUniversalTime().ToString("o")
715
715
  }
716
716
  Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 4)
717
717
  Write-DebugStage -Path $debugLogPath -Message "FRONTEND_SPAWNED"
@@ -739,17 +739,17 @@ if ($useRemoteAppServer) {
739
739
  if ($envFilePath -and $bootstrapScript) {
740
740
  try {
741
741
  Write-DebugStage -Path $debugLogPath -Message "LOADED_THREAD_WAIT_START"
742
- $loadedIds = @()
743
- for ($attempt = 1; $attempt -le 40; $attempt++) {
744
- $loaded = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "list-loaded", "--ws-url", $telegramAppServerWsUrl) -Attempts 1 -DelayMs 0
745
- $loadedIds = @($loaded.data)
746
- if ($loadedIds.Count -gt 0) {
747
- Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_SEEN attempt=" + $attempt + " count=" + $loadedIds.Count)
748
- break
749
- }
750
- if ($attempt -lt 40) {
751
- Start-Sleep -Milliseconds 500
752
- }
742
+ $loadedIds = @()
743
+ for ($attempt = 1; $attempt -le 40; $attempt++) {
744
+ $loaded = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "list-loaded", "--ws-url", $telegramAppServerWsUrl) -Attempts 1 -DelayMs 0
745
+ $loadedIds = @($loaded.data)
746
+ if ($loadedIds.Count -gt 0) {
747
+ Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_SEEN attempt=" + $attempt + " count=" + $loadedIds.Count)
748
+ break
749
+ }
750
+ if ($attempt -lt 40) {
751
+ Start-Sleep -Milliseconds 500
752
+ }
753
753
  }
754
754
  if ($loadedIds.Count -gt 0) {
755
755
  $activeThreadId = [string]$loadedIds[$loadedIds.Count - 1]
@@ -822,47 +822,47 @@ if ($useRemoteAppServer) {
822
822
  }
823
823
 
824
824
  $currentRuntime["thread_id"] = $activeThreadId
825
- Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
826
- Write-DebugStage -Path $debugLogPath -Message "CURRENT_RUNTIME_THREAD_WRITTEN"
827
-
828
- try {
829
- $verify = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "read-thread", "--ws-url", $telegramAppServerWsUrl, "--thread-id", $activeThreadId) -Attempts 2 -DelayMs 350
830
- if ($verify.ok -and $verify.threadId -eq $activeThreadId) {
831
- Write-DebugStage -Path $debugLogPath -Message "VERIFY_THREAD_OK"
832
- } else {
833
- Write-DebugStage -Path $debugLogPath -Message "VERIFY_THREAD_INVALID"
834
- }
835
- } catch {
836
- Write-DebugStage -Path $debugLogPath -Message ("VERIFY_THREAD_WARN " + $_.Exception.Message)
837
- }
838
-
839
- & node $sidecarManager | Out-Null
840
- if ($LASTEXITCODE -ne 0) {
841
- Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_FAILED"
842
- throw "Failed to ensure Telegram sidecars."
843
- }
844
- Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_OK"
845
-
846
- $pollerPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "poller.pid")
847
- $dispatcherPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "dispatcher.pid")
848
- $responderPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "responder.pid")
849
- $remoteSessionInfo = [ordered]@{
850
- ws_url = $telegramAppServerWsUrl
851
- thread_id = $activeThreadId
852
- pid = $backendProcess.Id
853
- started_at = (Get-Date).ToUniversalTime().ToString("o")
854
- sidecars = [ordered]@{
855
- poller = [ordered]@{ pid = $pollerPid }
856
- dispatcher = [ordered]@{ pid = $dispatcherPid }
857
- responder = [ordered]@{ pid = $responderPid }
858
- }
859
- }
860
- Write-TextFileWithRetry -Path $appServerInfoFile -Content ($remoteSessionInfo | ConvertTo-Json -Depth 6)
861
- if ($pollerPid -gt 0) { $currentRuntime["poller_pid"] = $pollerPid }
862
- if ($dispatcherPid -gt 0) { $currentRuntime["dispatcher_pid"] = $dispatcherPid }
863
- if ($responderPid -gt 0) { $currentRuntime["responder_pid"] = $responderPid }
864
- Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
865
- Write-DebugStage -Path $debugLogPath -Message "APP_SERVER_INFO_WRITTEN"
825
+ Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
826
+ Write-DebugStage -Path $debugLogPath -Message "CURRENT_RUNTIME_THREAD_WRITTEN"
827
+
828
+ try {
829
+ $verify = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "read-thread", "--ws-url", $telegramAppServerWsUrl, "--thread-id", $activeThreadId) -Attempts 2 -DelayMs 350
830
+ if ($verify.ok -and $verify.threadId -eq $activeThreadId) {
831
+ Write-DebugStage -Path $debugLogPath -Message "VERIFY_THREAD_OK"
832
+ } else {
833
+ Write-DebugStage -Path $debugLogPath -Message "VERIFY_THREAD_INVALID"
834
+ }
835
+ } catch {
836
+ Write-DebugStage -Path $debugLogPath -Message ("VERIFY_THREAD_WARN " + $_.Exception.Message)
837
+ }
838
+
839
+ & node $sidecarManager | Out-Null
840
+ if ($LASTEXITCODE -ne 0) {
841
+ Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_FAILED"
842
+ throw "Failed to ensure Telegram sidecars."
843
+ }
844
+ Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_OK"
845
+
846
+ $pollerPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "poller.pid")
847
+ $dispatcherPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "dispatcher.pid")
848
+ $responderPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "responder.pid")
849
+ $remoteSessionInfo = [ordered]@{
850
+ ws_url = $telegramAppServerWsUrl
851
+ thread_id = $activeThreadId
852
+ pid = $backendProcess.Id
853
+ started_at = (Get-Date).ToUniversalTime().ToString("o")
854
+ sidecars = [ordered]@{
855
+ poller = [ordered]@{ pid = $pollerPid }
856
+ dispatcher = [ordered]@{ pid = $dispatcherPid }
857
+ responder = [ordered]@{ pid = $responderPid }
858
+ }
859
+ }
860
+ Write-TextFileWithRetry -Path $appServerInfoFile -Content ($remoteSessionInfo | ConvertTo-Json -Depth 6)
861
+ if ($pollerPid -gt 0) { $currentRuntime["poller_pid"] = $pollerPid }
862
+ if ($dispatcherPid -gt 0) { $currentRuntime["dispatcher_pid"] = $dispatcherPid }
863
+ if ($responderPid -gt 0) { $currentRuntime["responder_pid"] = $responderPid }
864
+ Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
865
+ Write-DebugStage -Path $debugLogPath -Message "APP_SERVER_INFO_WRITTEN"
866
866
  } else {
867
867
  Write-DebugStage -Path $debugLogPath -Message "LOADED_THREAD_NONE"
868
868
  if ($sidecarsStartedEarly) {
@@ -881,12 +881,12 @@ if ($useRemoteAppServer) {
881
881
  Write-DebugStage -Path $debugLogPath -Message "APP_SERVER_INFO_WRITTEN_UNBOUND"
882
882
  }
883
883
  }
884
- } catch {
885
- Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_ERROR " + $_.Exception.Message)
886
- }
887
- }
888
- exit 0
889
- }
890
-
891
- & codex @codexArgs
892
- exit $LASTEXITCODE
884
+ } catch {
885
+ Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_ERROR " + $_.Exception.Message)
886
+ }
887
+ }
888
+ exit 0
889
+ }
890
+
891
+ & codex @codexArgs
892
+ exit $LASTEXITCODE