gemini-computer-use 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ops/update.ps1 DELETED
@@ -1,373 +0,0 @@
1
- # ==============================================================================
2
- # Antigravity Mesh - Windows node updater (single shot)
3
- #
4
- # Meant to be run by the Scheduled Task "AntigravityMeshUpdater" once a day, and
5
- # by hand through "gemini-computer-use update". It answers one question: is a
6
- # newer release published, and - unless -Check was given - install it.
7
- #
8
- # WHY A POWERSHELL WRAPPER
9
- # The updater itself is Python (core/updater.py), because it has to work on
10
- # Linux and macOS too. This wrapper exists for the two things only Windows
11
- # knows: which interpreter the node is actually pinned to (the Startup .vbs),
12
- # and where the payload lives (WshShell.CurrentDirectory). Starting the updater
13
- # with the WRONG interpreter - the Microsoft Store alias - was the failure that
14
- # kept a node offline for a day, so the interpreter is taken from the same
15
- # launcher the agent itself is started from and refused if it is an alias.
16
- #
17
- # WHAT IT DOES, IN ORDER
18
- # 1. resolves the install directory (-InstallDir, the Startup launcher, this
19
- # script's own location, the config directory - in that order) and refuses
20
- # a directory that holds no core\agent.py;
21
- # 2. resolves the interpreter (-Python, the Startup launcher's Run line, the
22
- # heartbeat, MESH_PYTHON, then a real python.exe on PATH) and refuses the
23
- # Microsoft Store alias, which never runs Python;
24
- # 3. runs "python -m core.updater --check" (with -Check) or "--apply",
25
- # capturing stdout and stderr separately so the JSON answer stays parseable
26
- # on Windows PowerShell 5.1;
27
- # 4. prints (or logs, with -Quiet) the local version, the newest published
28
- # version and what was done.
29
- #
30
- # EXIT CODES
31
- # 0 the node is on the newest release (or was updated without a restart)
32
- # 2 an update is available and was not installed (-Check), or the node is
33
- # misconfigured (no payload, no usable interpreter)
34
- # 3 the update failed: network, checksum mismatch, unusable payload
35
- # 4 the update was installed and the node is restarting
36
- #
37
- # PARAMETERS
38
- # -Check report only; never download or replace anything
39
- # -Force ignore the check interval and the failed-attempt backoff
40
- # -NoRestart install the payload but do not restart the agent
41
- # -Offline with -Check: report the cached answer, touch no network
42
- # -Json print the updater's JSON answer verbatim (for scripts)
43
- # -InstallDir payload directory override
44
- # -Python interpreter override
45
- # -ConfigDir where agent.env / agent.heartbeat live.
46
- # Default: %USERPROFILE%\.config\antigravity-mesh
47
- # -LogFile this script's journal. Default: <ConfigDir>\update-task.log
48
- # -Quiet write to -LogFile only, print nothing to the console
49
- #
50
- # This file is deliberately ASCII-only: Windows PowerShell 5.1 reads a BOM-less
51
- # script with the ANSI code page, so non-ASCII text here is a portability bug.
52
- #
53
- # Usage:
54
- # .\update.ps1
55
- # .\update.ps1 -Check
56
- # .\update.ps1 -Quiet -LogFile C:\logs\update.log
57
- # ==============================================================================
58
- [CmdletBinding()]
59
- param(
60
- [switch]$Check,
61
- [switch]$Force,
62
- [switch]$NoRestart,
63
- [switch]$Offline,
64
- [switch]$Json,
65
- [string]$InstallDir = '',
66
- [string]$Python = '',
67
- [string]$ConfigDir = '',
68
- [string]$LogFile = '',
69
- [switch]$Quiet
70
- )
71
-
72
- $ErrorActionPreference = 'Continue'
73
- $WindowsAppsMarker = '\WindowsApps\'
74
-
75
- $script:QuietMode = [bool]$Quiet
76
- $script:LogTarget = ''
77
- $script:Utf8NoBom = New-Object System.Text.UTF8Encoding -ArgumentList @($false)
78
-
79
- function Write-LogLine {
80
- param([string]$Text)
81
- if ([string]::IsNullOrEmpty($script:LogTarget)) { return }
82
- try {
83
- $stamp = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ssK')
84
- [System.IO.File]::AppendAllText($script:LogTarget, "[$stamp] $Text" + [Environment]::NewLine, $script:Utf8NoBom)
85
- } catch { }
86
- }
87
-
88
- function Write-Note {
89
- param([string]$Text)
90
- Write-LogLine $Text
91
- if (-not $script:QuietMode) { Write-Host $Text }
92
- }
93
-
94
- function Stop-WithConfigError {
95
- param([string]$Message)
96
- Write-Note ('CONFIG ERROR: ' + $Message)
97
- exit 2
98
- }
99
-
100
- function Resolve-FullPath {
101
- param([string]$Path)
102
- if ([string]::IsNullOrWhiteSpace($Path)) { return $Path }
103
- try { return [System.IO.Path]::GetFullPath($Path) } catch { return $Path }
104
- }
105
-
106
- function Test-File {
107
- # Test-Path refuses an empty -LiteralPath with a parameter binding error, and
108
- # that error aborts the whole statement it sits in - including the body of the
109
- # "if (Test-Path $x)" it guards. Every path check in this script goes through
110
- # here, so an unresolved value is simply "not a file".
111
- param([string]$Path)
112
- if ([string]::IsNullOrWhiteSpace($Path)) { return $false }
113
- return (Test-Path -LiteralPath $Path -PathType Leaf)
114
- }
115
-
116
- function Read-TextFile {
117
- param([string]$Path)
118
- $bytes = [System.IO.File]::ReadAllBytes($Path)
119
- if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
120
- return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
121
- }
122
- if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
123
- return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
124
- }
125
- if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
126
- return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
127
- }
128
- # The launcher is written as UTF-16 ("Unicode") by install.ps1, but a
129
- # hand-edited file may be ANSI or UTF-8, so a NUL byte decides.
130
- $hasNul = $false
131
- $limit = [Math]::Min($bytes.Length, 512)
132
- for ($i = 0; $i -lt $limit; $i++) {
133
- if ($bytes[$i] -eq 0) { $hasNul = $true; break }
134
- }
135
- if ($hasNul) { return [System.Text.Encoding]::Unicode.GetString($bytes) }
136
- return [System.Text.Encoding]::UTF8.GetString($bytes)
137
- }
138
-
139
- # ------------------------------------------------------------------ paths ----
140
- if ([string]::IsNullOrWhiteSpace($ConfigDir)) {
141
- $profileDir = $env:USERPROFILE
142
- if ([string]::IsNullOrWhiteSpace($profileDir)) { $profileDir = [Environment]::GetFolderPath('UserProfile') }
143
- if ([string]::IsNullOrWhiteSpace($profileDir)) {
144
- Stop-WithConfigError 'cannot resolve the user profile directory; pass -ConfigDir explicitly'
145
- }
146
- $ConfigDir = Join-Path $profileDir '.config\antigravity-mesh'
147
- }
148
- $ConfigDir = Resolve-FullPath $ConfigDir
149
- if ([string]::IsNullOrWhiteSpace($LogFile)) { $LogFile = Join-Path $ConfigDir 'update-task.log' }
150
- $LogFile = Resolve-FullPath $LogFile
151
- try {
152
- $logDir = Split-Path -Parent $LogFile
153
- if ($logDir -and -not (Test-Path -LiteralPath $logDir -PathType Container)) {
154
- $null = New-Item -ItemType Directory -Path $logDir -Force -ErrorAction Stop
155
- }
156
- $script:LogTarget = $LogFile
157
- } catch {
158
- $script:LogTarget = ''
159
- }
160
-
161
- # ------------------------------------------------------------- the launcher --
162
- # install.ps1 writes this file; it names both the interpreter to run and the
163
- # directory the agent runs from, which is exactly what the updater needs.
164
- $startupDir = [Environment]::GetFolderPath('Startup')
165
- $vbsPath = ''
166
- $launcherText = ''
167
- if (-not [string]::IsNullOrWhiteSpace($startupDir)) {
168
- $vbsPath = Join-Path $startupDir 'antigravity-agent.vbs'
169
- if (Test-Path -LiteralPath $vbsPath -PathType Leaf) {
170
- try { $launcherText = Read-TextFile $vbsPath } catch { $launcherText = '' }
171
- }
172
- }
173
-
174
- function Get-LauncherInterpreter {
175
- if ([string]::IsNullOrWhiteSpace($launcherText)) { return '' }
176
- $run = [regex]::Match($launcherText, '(?im)^[^\r\n]*WshShell\.Run[^\r\n]*?cmd[ \t]+/c[ \t]+"+(?<exe>[^"]+?)"+[ \t]+(?:-u[ \t]+)?-m[ \t]+core\.agent[^\r\n]*')
177
- if (-not $run.Success) {
178
- $run = [regex]::Match($launcherText, '(?im)^[^\r\n]*WshShell\.Run[^\r\n]*?(?<exe>[A-Za-z]:\\[^"]*?\.(?:exe|cmd|bat))')
179
- }
180
- if ($run.Success) { return $run.Groups['exe'].Value.Trim() }
181
- return ''
182
- }
183
-
184
- function Get-LauncherPayloadDir {
185
- if ([string]::IsNullOrWhiteSpace($launcherText)) { return '' }
186
- $dir = [regex]::Match($launcherText, '(?im)^[^\r\n]*WshShell\.CurrentDirectory[ \t]*=[ \t]*"(?<dir>[^"]+)"')
187
- if ($dir.Success) { return $dir.Groups['dir'].Value.Trim() }
188
- return ''
189
- }
190
-
191
- function Test-NodePayload {
192
- param([string]$Path)
193
- if ([string]::IsNullOrWhiteSpace($Path)) { return $false }
194
- return (Test-Path -LiteralPath (Join-Path $Path 'core\agent.py') -PathType Leaf)
195
- }
196
-
197
- # --- install directory --------------------------------------------------------
198
- $PayloadReason = '-InstallDir'
199
- if (-not (Test-NodePayload $InstallDir)) {
200
- $candidate = Get-LauncherPayloadDir
201
- if (Test-NodePayload $candidate) {
202
- $InstallDir = $candidate
203
- $PayloadReason = 'the Startup launcher (WshShell.CurrentDirectory)'
204
- } else {
205
- $candidate = ''
206
- if ($PSScriptRoot) { $candidate = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) }
207
- if (Test-NodePayload $candidate) {
208
- $InstallDir = $candidate
209
- $PayloadReason = 'the location of this script'
210
- } elseif (Test-NodePayload $ConfigDir) {
211
- $InstallDir = $ConfigDir
212
- $PayloadReason = 'the config directory'
213
- } else {
214
- Stop-WithConfigError ('no node payload found: -InstallDir, the Startup launcher and the script location all lack core\agent.py')
215
- }
216
- }
217
- }
218
- $InstallDir = Resolve-FullPath $InstallDir
219
- if (-not (Test-NodePayload $InstallDir)) {
220
- Stop-WithConfigError ('the install directory holds no core\agent.py: ' + $InstallDir)
221
- }
222
-
223
- # --- interpreter --------------------------------------------------------------
224
- function Get-HeartbeatInterpreter {
225
- $heartbeat = Join-Path $ConfigDir 'agent.heartbeat'
226
- if (-not (Test-Path -LiteralPath $heartbeat -PathType Leaf)) { return '' }
227
- try {
228
- $json = [System.IO.File]::ReadAllText($heartbeat) | ConvertFrom-Json
229
- } catch { return '' }
230
- $prop = $json.PSObject.Properties['interpreter']
231
- if ($prop -and $prop.Value) { return ([string]$prop.Value).Trim() }
232
- return ''
233
- }
234
-
235
- function Get-PythonOnPath {
236
- # The first "python" on PATH is usually the Microsoft Store alias, so every
237
- # candidate is checked and the alias is skipped rather than accepted.
238
- foreach ($cmd in @(Get-Command python -All -ErrorAction SilentlyContinue)) {
239
- if ($cmd -and $cmd.Source -and $cmd.Source -notlike "*$WindowsAppsMarker*" -and (Test-Path -LiteralPath $cmd.Source -PathType Leaf)) {
240
- return $cmd.Source
241
- }
242
- }
243
- $candidates = @(
244
- "$env:LOCALAPPDATA\Programs\Python\Python313\python.exe",
245
- "$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
246
- "$env:LOCALAPPDATA\Programs\Python\Python311\python.exe",
247
- "$env:ProgramFiles\Python313\python.exe",
248
- "$env:ProgramFiles\Python312\python.exe",
249
- "$env:ProgramFiles\Python311\python.exe"
250
- )
251
- foreach ($candidate in $candidates) {
252
- if (Test-Path -LiteralPath $candidate -PathType Leaf) { return $candidate }
253
- }
254
- return ''
255
- }
256
-
257
- $InterpreterReason = '-Python'
258
- if (-not (Test-File $Python)) {
259
- $Python = ''
260
- $fromLauncher = Get-LauncherInterpreter
261
- if ($fromLauncher) {
262
- $Python = $fromLauncher
263
- $InterpreterReason = 'the Startup launcher (WshShell.Run)'
264
- }
265
- if (-not (Test-File $Python)) {
266
- $Python = Get-HeartbeatInterpreter
267
- $InterpreterReason = 'the heartbeat'
268
- }
269
- if (-not (Test-File $Python)) {
270
- $Python = $env:MESH_PYTHON
271
- $InterpreterReason = 'MESH_PYTHON'
272
- }
273
- if (-not (Test-File $Python)) {
274
- $Python = Get-PythonOnPath
275
- $InterpreterReason = 'PATH'
276
- }
277
- }
278
- if ([string]::IsNullOrWhiteSpace($Python)) {
279
- Stop-WithConfigError 'no python.exe found: pass -Python, or re-run install.ps1 so the node pins a real interpreter'
280
- }
281
- if ($Python -like "*$WindowsAppsMarker*") {
282
- Stop-WithConfigError ('the resolved interpreter is the Microsoft Store alias: ' + $Python + ' - that alias never runs Python; re-run install.ps1')
283
- }
284
- if (-not (Test-File $Python)) {
285
- Stop-WithConfigError ('the resolved interpreter does not exist: ' + $Python)
286
- }
287
-
288
- Write-Note ('updater start: install=' + $InstallDir + ' (' + $PayloadReason + ') interpreter=' + $Python + ' (' + $InterpreterReason + ') mode=' + $(if ($Check) { 'check' } else { 'apply' }))
289
-
290
- # --------------------------------------------------------------- run Python --
291
- # stdout and stderr are redirected to files by cmd.exe, not captured by
292
- # Start-Process: on Windows PowerShell 5.1 a process started that way reports an
293
- # empty ExitCode once it has exited, and mixing stderr into stdout would break
294
- # the JSON answer the updater prints.
295
- $outFile = Join-Path $env:TEMP ('mesh-update-out-' + [Guid]::NewGuid().ToString('N') + '.json')
296
- $errFile = Join-Path $env:TEMP ('mesh-update-err-' + [Guid]::NewGuid().ToString('N') + '.log')
297
- if ($Check) { $mode = '--check' } else { $mode = '--apply' }
298
- $arguments = @('-m', 'core.updater', $mode, '--json', '--dir', $InstallDir)
299
- if ($Force) { $arguments += '--force' }
300
- if ($Offline -and $Check) { $arguments += '--offline' }
301
- if ($NoRestart -and -not $Check) { $arguments += '--no-restart' }
302
-
303
- $quoted = @($arguments | ForEach-Object { '"' + ($_ -replace '"', '\"') + '"' }) -join ' '
304
- $commandLine = '/c ""' + $Python + '" ' + $quoted + ' > "' + $outFile + '" 2> "' + $errFile + '""'
305
- $exitCode = 3
306
- try {
307
- $null = & cmd.exe $commandLine
308
- $exitCode = $LASTEXITCODE
309
- } catch {
310
- Write-Note ('could not start the interpreter: ' + $_.Exception.Message)
311
- exit 3
312
- }
313
-
314
- $answer = ''
315
- if (Test-Path -LiteralPath $outFile) {
316
- try { $answer = ([System.IO.File]::ReadAllText($outFile)).Trim() } catch { $answer = '' }
317
- }
318
- $errors = ''
319
- if (Test-Path -LiteralPath $errFile) {
320
- try { $errors = ([System.IO.File]::ReadAllText($errFile)).Trim() } catch { $errors = '' }
321
- }
322
- Remove-Item -LiteralPath $outFile, $errFile -Force -ErrorAction SilentlyContinue
323
-
324
- $parsed = $null
325
- if ($answer) {
326
- try { $parsed = $answer | ConvertFrom-Json } catch { $parsed = $null }
327
- }
328
-
329
- # The updater's own log lines (a traceback, a watchdog note) belong in the
330
- # journal: they are the only trace when --apply fails before printing JSON.
331
- if ($errors) {
332
- foreach ($line in @($errors -split "`r?`n")) {
333
- if ($line.Trim()) { Write-LogLine ('python: ' + $line.Trim()) }
334
- }
335
- }
336
-
337
- if ($parsed) {
338
- $current = [string]$parsed.current
339
- $latest = [string]$parsed.latest
340
- $reason = [string]$parsed.reason
341
- if ($Json) {
342
- # The updater's own answer, verbatim: scripts read this, so it is not
343
- # decorated with the human summary below.
344
- Write-Output $answer
345
- } else {
346
- if ($Check) {
347
- if ($exitCode -eq 2) {
348
- Write-Note ('update available: ' + $current + ' -> ' + $latest + ' (' + $reason + ')')
349
- } elseif ($exitCode -eq 0) {
350
- Write-Note ('up to date: ' + $current + ' (' + $reason + ')')
351
- } else {
352
- Write-Note ('check failed: ' + $reason)
353
- }
354
- } else {
355
- if ($exitCode -eq 0 -or $exitCode -eq 4) {
356
- Write-Note ('updated: ' + $reason + ' (exit ' + $exitCode + ')')
357
- } else {
358
- Write-Note ('update failed: ' + $reason)
359
- }
360
- }
361
- if (-not $script:QuietMode) {
362
- if ($latest) { Write-Host ('latest : ' + $latest) }
363
- if ($reason) { Write-Host ('detail : ' + $reason) }
364
- }
365
- }
366
- } elseif (-not $script:QuietMode -and -not $Json) {
367
- Write-Host ('the updater produced no JSON answer (exit ' + $exitCode + ')')
368
- if ($errors) { Write-Host $errors }
369
- }
370
-
371
- if ($Check -and $exitCode -eq 0 -and $parsed -and $parsed.update_available) { exit 2 }
372
- if ($exitCode -eq 2 -and -not $Check) { exit 2 }
373
- exit $exitCode
@@ -1,298 +0,0 @@
1
- # Windows agent watchdog and doctor
2
-
3
- | File | Role |
4
- | :--- | :--- |
5
- | `agent-watchdog.ps1` | single-shot supervisor: check the node, restart the agent if it is dead |
6
- | `../doctor.ps1` | read-only diagnostic: report every fact that explains an offline node |
7
- | `../update.ps1` | single-shot updater: install a newer release and let the node restart onto it |
8
- | `run-hidden.vbs` | the windowless launcher both scheduled tasks go through: no console is ever drawn |
9
-
10
- All three PowerShell scripts are ASCII-only, target Windows PowerShell 5.1 and use
11
- nothing but in-box cmdlets; `run-hidden.vbs` is ASCII-only too and needs only
12
- `wscript.exe`. None of them needs the task scheduler to be running in order to work.
13
-
14
- ## Why `run-hidden.vbs` exists
15
-
16
- A scheduled task has no console of its own. When the action is `powershell.exe`,
17
- Windows therefore allocates a **new** console for it and only then applies
18
- `-WindowStyle Hidden`: the window is created, drawn and hidden again, and the user
19
- sees a black window appear and disappear. The watchdog fires every five minutes, so
20
- that flash is the most noticeable thing this project does on a desktop.
21
-
22
- `wscript.exe` is a GUI host and never allocates a console, and
23
- `WshShell.Run command, 0, False` hands the child `SW_HIDE` in its `STARTUPINFO` —
24
- the window is hidden from the first moment, so nothing is ever painted. That is the
25
- same mechanism as the logon launcher (`antigravity-agent.vbs`), which is why a node
26
- starts without a window either. Output goes to
27
- `<ConfigDir>\<script name>.out.log`, because a window that never appears also never
28
- shows an error.
29
-
30
- ```powershell
31
- # exactly how the two tasks call it; the script path is relative to the payload
32
- wscript.exe "$Payload\ops\windows\run-hidden.vbs" "ops\windows\agent-watchdog.ps1" -Quiet
33
- wscript.exe "$Payload\ops\windows\run-hidden.vbs" "ops\update.ps1" -Quiet
34
- ```
35
-
36
- ## Why they exist
37
-
38
- After a Windows Update reboot the logon launcher did nothing, and the node stayed
39
- offline for about 29 hours. The interpreter pinned in the Startup `.vbs` was the
40
- Microsoft Store App Execution Alias `<...>\WindowsApps\python.exe` - a file that
41
- exists and starts, but runs no Python at all. The only trace was a truncated
42
- `Python ` fragment appended to `agent.log` with no trailing newline.
43
-
44
- The watchdog therefore **refuses any interpreter whose path contains
45
- `\WindowsApps\`**, and the doctor surfaces the same fact (plus the torn log line)
46
- within seconds.
47
-
48
- ## `agent-watchdog.ps1` - single-shot supervisor
49
-
50
- Run every 5 minutes by the Scheduled Task **`AntigravityMeshWatchdog`**.
51
-
52
- | Parameter | Default | Meaning |
53
- | :--- | :--- | :--- |
54
- | `-ConfigDir` | `%USERPROFILE%\.config\antigravity-mesh` | where `agent.env`, `agent.log`, `agent.heartbeat` live |
55
- | `-LogFile` | `<ConfigDir>\watchdog.log` | the watchdog's own journal |
56
- | `-GraceSeconds` | `60` | a heartbeat younger than this counts as alive |
57
- | `-Quiet` | off | log only, no console output |
58
-
59
- Liveness is checked in this order, and the first positive answer wins:
60
-
61
- 1. `<ConfigDir>\agent.heartbeat` is valid JSON and its `ts` is newer than
62
- `-GraceSeconds`;
63
- 2. `GET https://<gateway>/health?user=<node>` reports `node_online: true`;
64
- 3. a verified agent process is running: a `python.exe`/`pythonw.exe`/`py.exe` with
65
- `-m core.agent` as a real argument, or the `cmd.exe` launcher wrapper that also
66
- carries the `agent.log` redirect. A process that merely mentions `core.agent`
67
- (a PowerShell session, a grep) never counts - otherwise a dead node would look
68
- alive and never be restarted.
69
-
70
- When the node is dead it appends **exactly one** diagnostic line to `agent.log`
71
- (reason + interpreter being started), starts the agent hidden with the same
72
- command line, environment and working directory as the `.vbs`
73
- (`cmd /c ""<python.exe>" -u -m core.agent >> "<agent.log>" 2>&1`), waits up to 20 s
74
- for liveness and journals the outcome.
75
-
76
- | Exit code | Meaning |
77
- | :--- | :--- |
78
- | `0` | the node is online (already, or recovered by this run); also when another watchdog instance is handling it |
79
- | `2` | configuration or launcher problem: `agent.env`, the Startup `.vbs` or the pinned interpreter is missing, unusable, or is the Store alias |
80
- | `3` | the node is still offline after the recovery attempt |
81
-
82
- It is idempotent: a live node (or a live `core.agent` process) is never started
83
- twice, and a named mutex serialises overlapping runs. The token is never printed
84
- and never logged - it is masked as `****`.
85
-
86
- ## `../doctor.ps1` - read-only diagnostic
87
-
88
- | Parameter | Default | Meaning |
89
- | :--- | :--- | :--- |
90
- | `-ConfigDir` | `%USERPROFILE%\.config\antigravity-mesh` | same directory as above |
91
- | `-Json` | off | print exactly one JSON object and nothing else |
92
- | `-TailLines` | `20` | how many `agent.log` lines to show |
93
-
94
- It reports the interpreter from the `.vbs` (with `--version` and an
95
- `import websockets` probe, and whether it is the Store alias), the `.vbs` path and
96
- content with the token masked, the state of `AntigravityMeshWatchdog`, `agent.env`
97
- with the token masked, the heartbeat JSON and its age, the last `-TailLines` lines
98
- of `agent.log` with `torn-line: true` when the file does not end in a newline, the
99
- gateway `/health` JSON, and whether a verified `core.agent` process is running
100
- (the same rule the watchdog applies: a Python interpreter running `-m core.agent`,
101
- never a process that merely mentions it).
102
-
103
- It finishes with the verdict lines `interpreter_ok`, `autostart_ok`,
104
- `heartbeat_fresh`, `node_online`. Exit code `0` means all of `interpreter_ok`,
105
- `autostart_ok` and `node_online` are true; anything else is `2`. Nothing is
106
- written to disk.
107
-
108
- ## Running them by hand
109
-
110
- ```powershell
111
- # from the payload / repository root
112
- .\ops\doctor.ps1
113
- .\ops\doctor.ps1 -Json
114
- .\ops\doctor.ps1 -TailLines 50
115
- .\ops\windows\agent-watchdog.ps1
116
- .\ops\windows\agent-watchdog.ps1 -Quiet
117
-
118
- # through the CLI (same scripts, found next to bin/cli.js)
119
- npx gemini-computer-use doctor
120
- npx gemini-computer-use doctor --json
121
- npx gemini-computer-use restart
122
-
123
- # the scheduled task
124
- Get-ScheduledTask -TaskName AntigravityMeshWatchdog
125
- Get-ScheduledTaskInfo -TaskName AntigravityMeshWatchdog
126
- Start-ScheduledTask -TaskName AntigravityMeshWatchdog
127
- ```
128
-
129
- ## What the installer must register
130
-
131
- Task name: **`AntigravityMeshWatchdog`**.
132
-
133
- * **Action**: `wscript.exe`
134
- * **Arguments**: `"<payload>\ops\windows\run-hidden.vbs" "ops\windows\agent-watchdog.ps1" -Quiet`
135
- * **Working directory**: the payload directory (the directory that contains
136
- `core\`, next to `install.ps1`). The launcher resolves the script against its own
137
- location, so the registered path survives a payload that moves.
138
- * **Triggers**: once at logon of the installing user, plus a repeating trigger
139
- (`-Once -RepetitionInterval 5 minutes`, indefinite duration). The logon trigger
140
- mirrors the `.vbs`; the repeating trigger is what actually recovers the node, and
141
- `-StartWhenAvailable` makes up a run missed during a reboot.
142
- * **Settings**: `-MultipleInstances IgnoreNew`, `-StartWhenAvailable`,
143
- `-ExecutionTimeLimit 10 minutes`.
144
-
145
- ```powershell
146
- $PayloadDir = Split-Path -Parent $MyInvocation.MyCommand.Path
147
- $Launcher = Join-Path $PayloadDir 'ops\windows\run-hidden.vbs'
148
- $action = New-ScheduledTaskAction -Execute 'wscript.exe' `
149
- -Argument ('"' + $Launcher + '" "ops\windows\agent-watchdog.ps1" -Quiet') `
150
- -WorkingDirectory $PayloadDir
151
- $atLogon = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
152
- $repeat = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(2) `
153
- -RepetitionInterval (New-TimeSpan -Minutes 5) `
154
- -RepetitionDuration ([TimeSpan]::MaxValue)
155
- $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries `
156
- -DontStopIfGoingOnBatteries -StartWhenAvailable `
157
- -MultipleInstances IgnoreNew `
158
- -ExecutionTimeLimit (New-TimeSpan -Minutes 10)
159
- Register-ScheduledTask -TaskName 'AntigravityMeshWatchdog' -Action $action `
160
- -Trigger @($atLogon, $repeat) -Settings $settings -Force `
161
- -Description 'Antigravity Mesh: keep the node agent running (every 5 minutes).'
162
- ```
163
-
164
- The `ops\` directory must travel with the payload: it has to be listed in
165
- `package.json` (`files`), in `build-installer-exe.ps1` (`$PAYLOAD`) and in
166
- whatever `deploy_gateway.sh` stages for the node bootstrap.
167
-
168
- ## `../update.ps1` - single-shot self-update
169
-
170
- Run once a day by the Scheduled Task **`AntigravityMeshUpdater`**, and by hand as
171
- `gemini-computer-use update [-Check]`.
172
-
173
- | Parameter | Default | Meaning |
174
- | :--- | :--- | :--- |
175
- | `-Check` | off | report only; never download or replace anything |
176
- | `-Force` | off | ignore the check interval and the failed-attempt backoff |
177
- | `-NoRestart` | off | install the payload but do not restart the agent |
178
- | `-Offline` | off | with `-Check`: report the cached answer, touch no network |
179
- | `-Json` | off | print the updater's JSON answer verbatim |
180
- | `-InstallDir` | from the Startup `.vbs` | payload directory |
181
- | `-Python` | from the Startup `.vbs` | interpreter to run the updater with |
182
- | `-ConfigDir` | `%USERPROFILE%\.config\antigravity-mesh` | where `agent.env` and `agent.heartbeat` live |
183
- | `-LogFile` | `<ConfigDir>\update-task.log` | this script's own journal |
184
- | `-Quiet` | off | log only, no console output |
185
-
186
- It resolves the install directory and the interpreter the same way the watchdog
187
- does - from the Startup launcher `install.ps1` wrote - and **refuses the Microsoft
188
- Store alias** for the same reason: that alias never runs Python. The work itself is
189
- done by `python -m core.updater` (`--check` or `--apply`), which downloads the
190
- release payload, verifies its published SHA-256, swaps it in with a rollback backup
191
- and starts a helper that brings the agent back. See
192
- [docs/UPDATES.md](../../docs/UPDATES.md).
193
-
194
- | Exit code | Meaning |
195
- | :--- | :--- |
196
- | `0` | the node is on the newest release, or was updated without a restart |
197
- | `2` | an update exists and was not installed (`-Check`), or the node is misconfigured |
198
- | `3` | the update failed: network, checksum mismatch, unusable payload |
199
- | `4` | the update was installed and the node is restarting |
200
-
201
- Register it as **`AntigravityMeshUpdater`**: action `wscript.exe` (through
202
- `run-hidden.vbs`, so the daily run never shows a window), arguments
203
- `"<payload>\ops\windows\run-hidden.vbs" "ops\update.ps1" -Quiet`, working directory
204
- the payload directory, trigger daily (the installer uses 03:30), settings
205
- `-MultipleInstances IgnoreNew` and `-StartWhenAvailable` (so a machine that was off
206
- at 03:30 updates at the next opportunity).
207
-
208
- ---
209
-
210
- # Сторож и доктор для Windows
211
-
212
- | Файл | Назначение |
213
- | :--- | :--- |
214
- | `agent-watchdog.ps1` | однократная проверка: если узел мёртв - поднять агента |
215
- | `../doctor.ps1` | диагностика только на чтение: показать всё, что объясняет простой |
216
-
217
- Оба скрипта - ASCII-only, рассчитаны на Windows PowerShell 5.1 и используют
218
- только встроенные командлеты. Планировщик заданий для их работы не нужен.
219
-
220
- **Зачем это нужно.** После перезагрузки (Windows Update) автозапуск молча ничего
221
- не сделал, и узел оставался offline около 29 часов. В `.vbs` был прописан
222
- интерпретатор-алиас Microsoft Store `<...>\WindowsApps\python.exe`: файл
223
- существует и запускается, но Python не выполняет. Единственным следствием была
224
- обрезанная строка `Python ` в `agent.log` без завершающего перевода строки.
225
-
226
- Поэтому сторож **отказывается работать с интерпретатором, чей путь содержит
227
- `\WindowsApps\`**, а доктор за секунды показывает и это, и оборванную строку
228
- журнала.
229
-
230
- ## `agent-watchdog.ps1`
231
-
232
- Запускается каждые 5 минут заданием планировщика **`AntigravityMeshWatchdog`**.
233
-
234
- | Параметр | Значение по умолчанию | Смысл |
235
- | :--- | :--- | :--- |
236
- | `-ConfigDir` | `%USERPROFILE%\.config\antigravity-mesh` | где лежат `agent.env`, `agent.log`, `agent.heartbeat` |
237
- | `-LogFile` | `<ConfigDir>\watchdog.log` | собственный журнал сторожа |
238
- | `-GraceSeconds` | `60` | heartbeat моложе этого возраста считается живым |
239
- | `-Quiet` | выключен | только журнал, без вывода в консоль |
240
-
241
- Признаки жизни проверяются по порядку, побеждает первый положительный:
242
- сердцебиение `agent.heartbeat` (свежее), затем `GET https://<gateway>/health?user=<узел>`
243
- с `node_online: true`, затем подтверждённый процесс агента: `python.exe`/`pythonw.exe`/`py.exe`
244
- с настоящим аргументом `-m core.agent` либо обёртка `cmd.exe`, в которой есть
245
- перенаправление вывода в `agent.log`. Процесс, который лишь упоминает `core.agent`
246
- (сессия PowerShell, grep), агентом не считается - иначе мёртвый узел выглядел бы
247
- живым и никогда не перезапускался.
248
-
249
- Если узел мёртв, сторож дописывает в `agent.log` **ровно одну** строку с причиной
250
- и путём интерпретатора, скрытно запускает агента ровно так же, как это делает
251
- `.vbs`, ждёт до 20 секунд и записывает результат в `-LogFile`.
252
-
253
- Коды возврата: `0` - узел онлайн (был или восстановлен); `2` - проблема
254
- конфигурации или автозапуска (`agent.env`, `.vbs` или интерпретатор отсутствуют,
255
- непригодны либо это алиас Store); `3` - узел так и остался offline. Токен никогда
256
- не печатается и не пишется в журнал: он маскируется как `****`.
257
-
258
- ## `../doctor.ps1`
259
-
260
- | Параметр | Значение по умолчанию | Смысл |
261
- | :--- | :--- | :--- |
262
- | `-ConfigDir` | `%USERPROFILE%\.config\antigravity-mesh` | тот же каталог |
263
- | `-Json` | выключен | вывести ровно один JSON-объект и ничего больше |
264
- | `-TailLines` | `20` | сколько последних строк `agent.log` показать |
265
-
266
- Доктор показывает интерпретатор из `.vbs` (плюс `--version` и проверку
267
- `import websockets`, плюс признак алиаса Store), путь и содержимое `.vbs` с
268
- замаскированным токеном, состояние задания `AntigravityMeshWatchdog`, `agent.env`
269
- с замаскированным токеном, JSON сердцебиения и его возраст, последние строки
270
- `agent.log` с флагом `torn-line: true` (файл не заканчивается переводом строки),
271
- JSON ответа `/health` и наличие подтверждённого процесса агента.
272
- Завершается строки-вердикты
273
- `interpreter_ok`, `autostart_ok`, `heartbeat_fresh`, `node_online`. Код возврата
274
- `0` - всё в порядке, иначе `2`. На диск ничего не записывается.
275
-
276
- ## Ручной запуск
277
-
278
- ```powershell
279
- .\ops\doctor.ps1
280
- .\ops\doctor.ps1 -Json
281
- .\ops\windows\agent-watchdog.ps1
282
- .\ops\windows\agent-watchdog.ps1 -Quiet
283
- npx gemini-computer-use doctor
284
- npx gemini-computer-use restart
285
- Get-ScheduledTaskInfo -TaskName AntigravityMeshWatchdog
286
- Start-ScheduledTask -TaskName AntigravityMeshWatchdog
287
- ```
288
-
289
- Установщик регистрирует задание **`AntigravityMeshWatchdog`**: действие
290
- `powershell.exe` с аргументами
291
- `-NoProfile -ExecutionPolicy Bypass -File "<payload>\ops\windows\agent-watchdog.ps1" -Quiet`,
292
- рабочий каталог - каталог payload (там, где лежит `core\`), триггеры - при входе
293
- пользователя и повтор каждые 5 минут без ограничения длительности, настройки -
294
- `-MultipleInstances IgnoreNew`, `-StartWhenAvailable`,
295
- `-ExecutionTimeLimit 10 минут`. Готовый вызов `Register-ScheduledTask` приведён в
296
- английской части выше. Каталог `ops\` должен попадать в payload: его нужно указать
297
- в `package.json` (`files`), в `build-installer-exe.ps1` (`$PAYLOAD`) и в том, что
298
- `deploy_gateway.sh` выкладывает для узла.