@blunking/codexlink 0.1.0 → 0.1.2

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