Compare commits

...
Author SHA1 Message Date
arkon 5adf044399 chore: version packages 2026-06-09 03:54:17 +02:00
arkonandClaude Opus 4.8 d95b4c597c feat(self-update): live progress during install/build so it doesn't look hung
The updater wrote the status once per phase, so the minute-plus npm install and
build steps left the UI frozen on a single label. Add:

- a heartbeat in scripts/self-update.sh (run_step wrapper) that refreshes
  update-status.json every ~3s during the install/build steps with the latest
  output line; full output is still mirrored to the update log.
- a frontend (settings-ui.js) that, during non-terminal phases, shows the live
  status message plus a ticking total-elapsed counter instead of only the static
  phase label.

Takes effect when updating FROM a build that contains it — the detached runner
script (staged from scripts/self-update.sh) and the polling frontend are both
the from-version's copies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 03:36:39 +02:00
6 changed files with 58 additions and 10 deletions
+10
View File
@@ -1,5 +1,15 @@
# aicodeman
## 0.9.6
### Patch Changes
- Self-updater: show live progress during the slow steps so an update no longer looks frozen.
- The detached update runner (`scripts/self-update.sh`) now emits a heartbeat every few seconds during `npm install` and `npm run build`, refreshing the update status with the latest output line (full output is still written to the update log).
- App Settings → Updates now shows the live status message plus a ticking elapsed-time counter during non-terminal phases, instead of only a static phase label.
This takes effect when updating _from_ a build that includes it — the detached runner script and the polling UI are both the from-version's copies.
## 0.9.5
### Patch Changes
+1 -1
View File
@@ -56,7 +56,7 @@ When user says "COM":
CI runs `npm run check:lockfile` on every push/PR, so lockfile drift fails the build even if the `version-packages` script is bypassed.
**Version**: 0.9.5 (must match `package.json`)
**Version**: 0.9.6 (must match `package.json`)
## Project Overview
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "aicodeman",
"version": "0.9.5",
"version": "0.9.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "aicodeman",
"version": "0.9.5",
"version": "0.9.6",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "aicodeman",
"version": "0.9.5",
"version": "0.9.6",
"description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence",
"type": "module",
"main": "dist/index.js",
+33 -5
View File
@@ -93,6 +93,35 @@ write_status() {
' || echo "[self-update] WARN: status write failed ($phase)"
}
# Run a slow step with a heartbeat so the status file (and the UI polling it) keeps
# moving instead of looking frozen during npm install / build. Every few seconds it
# refreshes the status with the latest output line, and mirrors full output to the
# log. Returns the wrapped command's exit code.
run_step() {
local phase="$1" base="$2"; shift 2
local step_log; step_log="$(mktemp "${TMPDIR:-/tmp}/codeman-update.XXXXXX" 2>/dev/null || echo "/tmp/codeman-update.$$")"
write_status "$phase" "$base…"
echo "[self-update] $phase: $* (output below)"
"$@" >"$step_log" 2>&1 &
local pid=$! start=$SECONDS last_line=""
while kill -0 "$pid" 2>/dev/null; do
sleep 3
local line
line="$(tr -d '\r' <"$step_log" 2>/dev/null | grep -aE '[^[:space:]]' | tail -n 1 | cut -c1-100)"
[[ -n "$line" && "$line" != "$last_line" ]] && last_line="$line"
if [[ -n "$last_line" ]]; then
write_status "$phase" "$base… · $last_line"
else
write_status "$phase" "$base… (working)"
fi
done
wait "$pid"; local rc=$?
echo "[self-update] $phase finished in $((SECONDS - start))s (rc=$rc)"
cat "$step_log" >>"$LOG" 2>/dev/null || true
rm -f "$step_log" 2>/dev/null || true
return $rc
}
fail() {
local msg="$1" err="${2:-}"
echo "[self-update] FAILED: $msg ($err)"
@@ -138,13 +167,12 @@ git fetch --tags --force origin "refs/tags/$TAG:refs/tags/$TAG" 2>/dev/null \
write_status "checkout" "Checking out $TAG…"
git -c advice.detachedHead=false checkout --force "$TAG" || rollback_and_fail "Could not check out $TAG"
# 4) Install dependencies.
write_status "installing" "Installing dependencies…"
npm install --no-fund --no-audit || rollback_and_fail "Dependency install failed"
# 4) Install dependencies (heartbeat keeps the UI live during this slow step).
run_step "installing" "Installing dependencies" npm install --no-fund --no-audit \
|| rollback_and_fail "Dependency install failed"
# 5) Build (gate the restart on success — never restart into a torn dist/).
write_status "building" "Building…"
npm run build || rollback_and_fail "Build failed"
run_step "building" "Building" npm run build || rollback_and_fail "Build failed"
# 6) Restart the service so the new code loads. Write the terminal pre-restart
# marker FIRST so the freshly-booted server can reconcile it deterministically.
+11 -1
View File
@@ -606,7 +606,17 @@ Object.assign(CodemanApp.prototype, {
return;
}
if (!terminal.has(data.phase)) {
this._setUpdateProgress(`↻ ${escapeHtml(this._updatePhaseText(data.phase))}`);
// Prefer the live status message — the updater's heartbeat enriches it with
// the latest npm/build output line so a slow step doesn't look frozen — and
// fall back to the static phase label. Append total elapsed so the counter
// keeps ticking between heartbeats: a clear "still working" signal.
const label = (data.message && data.message.trim()) ? data.message.trim() : this._updatePhaseText(data.phase);
let elapsed = '';
if (data.startedAt) {
const secs = Math.max(0, Math.round((Date.now() - data.startedAt) / 1000));
elapsed = ` <span style="color:var(--text-secondary)">· ${secs}s</span>`;
}
this._setUpdateProgress(`<span class="tunnel-spinner"></span> ${escapeHtml(label)}${elapsed}`);
return;
}
this._stopUpdatePolling();