Sync agent state from PC at 2026-05-15 12:08:01
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
param(
|
||||
[int]$IntervalSeconds = 30,
|
||||
[int]$HeartbeatEverySeconds = 60,
|
||||
[switch]$Once
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
$StateDir = Join-Path $RepoRoot ".sync-state"
|
||||
$ErrorFile = Join-Path $StateDir "sync-error-pc.md"
|
||||
$LocalStateDir = Join-Path $RepoRoot ".sync-state"
|
||||
$PublicStateDir = Join-Path $RepoRoot "sync-state"
|
||||
$HeartbeatFile = Join-Path $PublicStateDir "heartbeat-pc.json"
|
||||
$ErrorFile = Join-Path $PublicStateDir "error-pc.md"
|
||||
$AllowedPaths = @(
|
||||
"AGENT_BOARD.md",
|
||||
"README.md",
|
||||
@@ -17,12 +20,16 @@ $AllowedPaths = @(
|
||||
"rollback",
|
||||
"tasks",
|
||||
"sync",
|
||||
"sync-state",
|
||||
".gitattributes",
|
||||
".gitignore"
|
||||
)
|
||||
|
||||
function Ensure-StateDir {
|
||||
if (-not (Test-Path $StateDir)) {
|
||||
New-Item -ItemType Directory -Path $StateDir | Out-Null
|
||||
foreach ($path in @($LocalStateDir, $PublicStateDir)) {
|
||||
if (-not (Test-Path $path)) {
|
||||
New-Item -ItemType Directory -Path $path | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +49,15 @@ Manual action required. Do not continue automatic sync until the repository is c
|
||||
"@ | Set-Content -Path $ErrorFile -Encoding UTF8
|
||||
}
|
||||
|
||||
function Get-GitOutput {
|
||||
param([string[]]$GitArgs)
|
||||
$output = & git @GitArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "git $($GitArgs -join ' ') failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
return $output
|
||||
}
|
||||
|
||||
function Invoke-Git {
|
||||
param([string[]]$GitArgs)
|
||||
& git @GitArgs
|
||||
@@ -79,6 +95,48 @@ function Test-ConflictMarkers {
|
||||
return -not [string]::IsNullOrWhiteSpace(($conflicts -join ""))
|
||||
}
|
||||
|
||||
function Get-RepoCleanStatus {
|
||||
$changes = & git -C $RepoRoot status --porcelain
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "git status failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace(($changes -join ""))) {
|
||||
return "clean"
|
||||
}
|
||||
return "dirty"
|
||||
}
|
||||
|
||||
function Test-HeartbeatDue {
|
||||
if ($Once -or -not (Test-Path $HeartbeatFile)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
$age = (Get-Date) - (Get-Item $HeartbeatFile).LastWriteTime
|
||||
return $age.TotalSeconds -ge $HeartbeatEverySeconds
|
||||
}
|
||||
|
||||
function Write-Heartbeat {
|
||||
Ensure-StateDir
|
||||
if (-not (Test-HeartbeatDue)) {
|
||||
return
|
||||
}
|
||||
|
||||
$head = (Get-GitOutput @("-C", $RepoRoot, "rev-parse", "--short", "HEAD")) -join ""
|
||||
$branch = (Get-GitOutput @("-C", $RepoRoot, "branch", "--show-current")) -join ""
|
||||
$status = Get-RepoCleanStatus
|
||||
$timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:sszzz"
|
||||
|
||||
[ordered]@{
|
||||
host = "pc"
|
||||
time = $timestamp
|
||||
branch = $branch
|
||||
head = $head
|
||||
status = $status
|
||||
intervalSeconds = $IntervalSeconds
|
||||
heartbeatEverySeconds = $HeartbeatEverySeconds
|
||||
} | ConvertTo-Json | Set-Content -Path $HeartbeatFile -Encoding UTF8
|
||||
}
|
||||
|
||||
function Sync-Once {
|
||||
Set-Location $RepoRoot
|
||||
Ensure-StateDir
|
||||
@@ -89,6 +147,8 @@ function Sync-Once {
|
||||
throw "merge or rebase conflicts detected"
|
||||
}
|
||||
|
||||
Write-Heartbeat
|
||||
|
||||
$allowedChanges = @(Get-AllowedChanges)
|
||||
if ($allowedChanges.Count -eq 0) {
|
||||
return
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
set -euo pipefail
|
||||
|
||||
INTERVAL_SECONDS="${INTERVAL_SECONDS:-30}"
|
||||
HEARTBEAT_EVERY_SECONDS="${HEARTBEAT_EVERY_SECONDS:-60}"
|
||||
ONCE="${1:-}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
STATE_DIR="$REPO_ROOT/.sync-state"
|
||||
ERROR_FILE="$STATE_DIR/sync-error-vps.md"
|
||||
LOCAL_STATE_DIR="$REPO_ROOT/.sync-state"
|
||||
PUBLIC_STATE_DIR="$REPO_ROOT/sync-state"
|
||||
HEARTBEAT_FILE="$PUBLIC_STATE_DIR/heartbeat-vps.json"
|
||||
ERROR_FILE="$PUBLIC_STATE_DIR/error-vps.md"
|
||||
|
||||
ALLOWED_PATHS=(
|
||||
"AGENT_BOARD.md"
|
||||
@@ -18,11 +21,13 @@ ALLOWED_PATHS=(
|
||||
"rollback"
|
||||
"tasks"
|
||||
"sync"
|
||||
"sync-state"
|
||||
".gitattributes"
|
||||
".gitignore"
|
||||
)
|
||||
|
||||
ensure_state_dir() {
|
||||
mkdir -p "$STATE_DIR"
|
||||
mkdir -p "$LOCAL_STATE_DIR" "$PUBLIC_STATE_DIR"
|
||||
}
|
||||
|
||||
write_sync_error() {
|
||||
@@ -41,6 +46,51 @@ Manual action required. Do not continue automatic sync until the repository is c
|
||||
EOF
|
||||
}
|
||||
|
||||
repo_clean_status() {
|
||||
if [[ -z "$(git status --porcelain)" ]]; then
|
||||
echo "clean"
|
||||
else
|
||||
echo "dirty"
|
||||
fi
|
||||
}
|
||||
|
||||
heartbeat_due() {
|
||||
if [[ "$ONCE" == "--once" || ! -f "$HEARTBEAT_FILE" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local now last age
|
||||
now="$(date +%s)"
|
||||
last="$(stat -c %Y "$HEARTBEAT_FILE")"
|
||||
age=$((now - last))
|
||||
[[ "$age" -ge "$HEARTBEAT_EVERY_SECONDS" ]]
|
||||
}
|
||||
|
||||
write_heartbeat() {
|
||||
ensure_state_dir
|
||||
if ! heartbeat_due; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local head branch status timestamp
|
||||
head="$(git rev-parse --short HEAD)"
|
||||
branch="$(git branch --show-current)"
|
||||
status="$(repo_clean_status)"
|
||||
timestamp="$(date '+%Y-%m-%dT%H:%M:%S%z')"
|
||||
|
||||
cat > "$HEARTBEAT_FILE" <<EOF
|
||||
{
|
||||
"host": "vps",
|
||||
"time": "$timestamp",
|
||||
"branch": "$branch",
|
||||
"head": "$head",
|
||||
"status": "$status",
|
||||
"intervalSeconds": $INTERVAL_SECONDS,
|
||||
"heartbeatEverySeconds": $HEARTBEAT_EVERY_SECONDS
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
has_allowed_changes() {
|
||||
local line path allowed
|
||||
while IFS= read -r line; do
|
||||
@@ -69,6 +119,8 @@ sync_once() {
|
||||
return 20
|
||||
fi
|
||||
|
||||
write_heartbeat
|
||||
|
||||
if ! has_allowed_changes; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user