feat: multi-agent-mux-update skill and in-place install (Issue #72)
The update is a skill (decision 4); deploy/update.sh and the installed .mam_deploy/update.sh are gone. - Phase 1 (installed code, update.sh): publish the update marker under the exclusive admission lock; refuse before any change while a running row, a live herdr agent in the workspace on any running server, an open job (unreadable JSON included, dry_run not), a live loop marker or a monitor.lock held by a monitor the skills did not start (U3) is there - the calling agent (server + pane, its row and one running job) excepted (lib_py/update.py precheck, decisions 1 and 7). Stop the monitor (TERM, then KILL and restart it if it does not end; decision 2), handle a leftover backup by its state (U4), back up .agents, .mam_deploy, root files, .venv, the installer's .mam files and the registry (SQLite backup API under the registry lock) into .mam/update-backup/, then exec a copy of install_phase.sh outside the tree (decision 5). - The copy (no lib.sh/lib_py, bash 3.2) runs the installer in place; on failure it restores the backup and restarts the monitor from the restored tree; on success it runs phase 2, post_update.sh of the new tree, which converts the registry with lib_py.registry_format and restarts the monitor if it ran. The backup and the marker are removed at the end. - install.sh updates in place (decisions 3, 4, 9): install_state keys are set, one line each; no updater is installed and an old one (.mam_deploy/update.sh, or a root update.sh the manifest lists) is removed; framework files the new version no longer ships are deleted when unmodified, kept and listed when modified, and dropped from base/, the manifest and asset_hashes.txt; a local source's .mam is not staged. remove.sh knows the new skill; CI shellchecks its scripts; docs point at the skill. - test_issue8_update_state and test_deploy_layout td6/td27 run the skill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6359d65999
commit
47fe24e07c
19 files changed
+1820
-314
No files matched your search
+67
-18
@@ -125,7 +125,9 @@ if [ "$REFRESH" -eq 1 ] || [ "${MAM_SKIP_VENV:-0}" -eq 1 ] || ! check_assets_pre
|
||||
FETCH_METHOD="archive"
|
||||
if [ -d "$REPO_URL" ]; then
|
||||
echo "🌐 Copying local working tree into a staging area..."
|
||||
cp -R "$REPO_URL/." "$STAGE_DIR/"
|
||||
# Everything but .mam/: runtime state is never staged, and it may hold the
|
||||
# update skill's rollback copy (.mam/update-backup/, Issue #72).
|
||||
find "$REPO_URL/." -mindepth 1 -maxdepth 1 ! -name .mam -exec cp -R {} "$STAGE_DIR/" \;
|
||||
FETCH_METHOD="local"
|
||||
elif command -v git &>/dev/null; then
|
||||
echo "🌐 Cloning repository (shallow) into a staging area..."
|
||||
@@ -157,9 +159,15 @@ if [ "$REFRESH" -eq 1 ] || [ "${MAM_SKIP_VENV:-0}" -eq 1 ] || ! check_assets_pre
|
||||
MANIFEST_FILE=".mam/install_manifest.txt"
|
||||
touch "$MANIFEST_FILE"
|
||||
|
||||
# Migrate legacy root layout for remove.sh and update.sh into .mam_deploy/ if owned
|
||||
# Drop every line naming this path from the manifest
|
||||
manifest_drop() {
|
||||
grep -Fvx -- "$1" "$MANIFEST_FILE" > "$MANIFEST_FILE.tmp" || true
|
||||
mv -f "$MANIFEST_FILE.tmp" "$MANIFEST_FILE"
|
||||
}
|
||||
|
||||
# Migrate legacy root layout for remove.sh into .mam_deploy/ if owned
|
||||
mkdir -p .mam_deploy
|
||||
for legacy_script in remove.sh update.sh; do
|
||||
for legacy_script in remove.sh; do
|
||||
if [ -f "$legacy_script" ] && grep -Fqx "$legacy_script" "$MANIFEST_FILE" 2>/dev/null; then
|
||||
mv -f "$legacy_script" ".mam_deploy/$legacy_script"
|
||||
python3 -c '
|
||||
@@ -179,6 +187,13 @@ with open(path, "w") as f:
|
||||
fi
|
||||
done
|
||||
|
||||
# update.sh is no longer shipped (Issue #72; the multi-agent-mux-update skill
|
||||
# updates). A legacy root update.sh goes only if the manifest says we put it there.
|
||||
if [ -f update.sh ] && grep -Fqx update.sh "$MANIFEST_FILE" 2>/dev/null; then
|
||||
rm -f update.sh
|
||||
manifest_drop update.sh
|
||||
fi
|
||||
|
||||
# Safe refresh & fingerprint checking logic
|
||||
TS=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
FRAMEWORK_LEDGER=".mam/.framework_ledger.tmp"
|
||||
@@ -374,7 +389,7 @@ MERGEREG
|
||||
fi
|
||||
done
|
||||
|
||||
# Install remove.sh and update.sh into .mam_deploy/
|
||||
# Install remove.sh into .mam_deploy/
|
||||
mkdir -p .mam_deploy
|
||||
if [ -f "$STAGE_DIR/deploy/remove.sh" ]; then
|
||||
cp "$STAGE_DIR/deploy/remove.sh" .mam_deploy/remove.sh
|
||||
@@ -384,13 +399,9 @@ MERGEREG
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$STAGE_DIR/deploy/update.sh" ]; then
|
||||
cp "$STAGE_DIR/deploy/update.sh" .mam_deploy/update.sh
|
||||
chmod 0755 .mam_deploy/update.sh
|
||||
if ! grep -Fqx ".mam_deploy/update.sh" "$MANIFEST_FILE" 2>/dev/null; then
|
||||
echo ".mam_deploy/update.sh" >> "$MANIFEST_FILE"
|
||||
fi
|
||||
fi
|
||||
# An older install's updater is not shipped any more (Issue #72)
|
||||
rm -f .mam_deploy/update.sh
|
||||
manifest_drop .mam_deploy/update.sh
|
||||
|
||||
if [ -f "$STAGE_DIR/.mam.env.example" ] && [ ! -e ".mam.env.example" ]; then
|
||||
cp "$STAGE_DIR/.mam.env.example" .
|
||||
@@ -407,6 +418,34 @@ MERGEREG
|
||||
fi
|
||||
fi
|
||||
|
||||
# Framework-owned files the previous install recorded (manifest and
|
||||
# asset_hashes.txt) that this version no longer ships (Issue #72). Unmodified
|
||||
# ones are deleted. Modified ones are kept and become the user's file: they
|
||||
# leave the manifest, so remove.sh leaves them alone, and asset_hashes.txt
|
||||
# (rebuilt below from the shipped files only).
|
||||
if [ -f .mam/asset_hashes.txt ]; then
|
||||
SHIPPED_LIST=".mam/.shipped_paths.tmp"
|
||||
cut -f2 "$FRAMEWORK_LEDGER" > "$SHIPPED_LIST"
|
||||
while read -r _old_sha _old_path; do
|
||||
[ -n "${_old_path:-}" ] || continue
|
||||
is_framework_owned "$_old_path" || continue
|
||||
grep -Fqx -- "$_old_path" "$MANIFEST_FILE" || continue
|
||||
if grep -Fqx -- "$_old_path" "$SHIPPED_LIST"; then continue; fi
|
||||
if [ -f "$_old_path" ] && [ "$(python3 -c 'import hashlib, sys; print(hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest())' "$_old_path")" != "$_old_sha" ]; then
|
||||
echo "ℹ️ Local modification detected (no longer shipped, kept as yours): $_old_path" >&2
|
||||
else
|
||||
rm -f "$_old_path"
|
||||
_old_dir="$(dirname "$_old_path")"
|
||||
while [ "$_old_dir" != ".agents" ] && rmdir "$_old_dir" 2>/dev/null; do
|
||||
_old_dir="$(dirname "$_old_dir")"
|
||||
done
|
||||
fi
|
||||
rm -f ".mam/base/$_old_path"
|
||||
manifest_drop "$_old_path"
|
||||
done < .mam/asset_hashes.txt
|
||||
rm -f "$SHIPPED_LIST"
|
||||
fi
|
||||
|
||||
# Re-build asset_hashes.txt from the UPSTREAM copy of every framework-owned file
|
||||
python3 - .mam/asset_hashes.txt "$FRAMEWORK_LEDGER" <<'HASHDB'
|
||||
import os, hashlib, sys
|
||||
@@ -590,15 +629,25 @@ with open(gi_path, "w") as f:
|
||||
PY
|
||||
|
||||
if [ "${MAM_SKIP_GITIGNORE:-0}" -eq 0 ]; then
|
||||
# Set the key: overwrite it in place, one line per key, other keys kept
|
||||
python3 -c '
|
||||
import os
|
||||
os.makedirs(".mam", exist_ok=True)
|
||||
lines = []
|
||||
if os.path.exists(".mam/install_state"):
|
||||
with open(".mam/install_state", "a") as f:
|
||||
f.write("gitignore_created=1\n")
|
||||
else:
|
||||
os.makedirs(".mam", exist_ok=True)
|
||||
with open(".mam/install_state", "w") as f:
|
||||
f.write("gitignore_created=1\n")
|
||||
with open(".mam/install_state") as f:
|
||||
lines = f.read().splitlines()
|
||||
out, done = [], False
|
||||
for line in lines:
|
||||
if line.startswith("gitignore_created="):
|
||||
if done:
|
||||
continue
|
||||
line, done = "gitignore_created=1", True
|
||||
out.append(line)
|
||||
if not done:
|
||||
out.append("gitignore_created=1")
|
||||
with open(".mam/install_state", "w") as f:
|
||||
f.write("\n".join(out) + "\n")
|
||||
'
|
||||
fi
|
||||
|
||||
@@ -617,6 +666,6 @@ cat <<EOF
|
||||
$ bash .agents/skills/multi-agent-mux-status/scripts/status.sh
|
||||
|
||||
3. Update installed skills in the future:
|
||||
$ bash .mam_deploy/update.sh
|
||||
$ bash .agents/skills/multi-agent-mux-update/scripts/update.sh
|
||||
--------------------------------------------------------------------------------
|
||||
EOF
|
||||
Reference in new issue
Block a user