agent-bios 0.14.0 → 0.15.0
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/claude/guides/claude-prompting.md +59 -7
- package/claude/guides/gpt-prompting.md +60 -4
- package/codex/guides/claude-prompting.md +59 -7
- package/codex/guides/gpt-prompting.md +60 -4
- package/compose/corpus-state.py +1170 -0
- package/compose/write-update-cache.py +53 -0
- package/install.sh +111 -6
- package/launch/agent-launch.py +112 -6
- package/launch/i18n/en.toml +1 -0
- package/launch/i18n/ja.toml +1 -0
- package/launch/i18n/ko.toml +1 -0
- package/package.json +4 -2
- package/provenance.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Write the update-check cache atomically.
|
|
3
|
+
|
|
4
|
+
The launcher READS this file on every start and never writes it, so a half-written
|
|
5
|
+
cache is a crash in the TUI rather than a stale badge — hence os.replace over a
|
|
6
|
+
temp file in the same directory, not a plain open-and-write.
|
|
7
|
+
|
|
8
|
+
`latest` is omitted when the lookup failed; `checked_at` is written regardless, so
|
|
9
|
+
an offline machine records its attempt and waits out the interval instead of
|
|
10
|
+
retrying on every launch. Absence of `latest` therefore means "asked, no answer",
|
|
11
|
+
which the launcher must not render as "up to date".
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv: list[str]) -> int:
|
|
20
|
+
if len(argv) != 5:
|
|
21
|
+
print("usage: write-update-cache.py <out> <checked_at> <latest|''> <current>",
|
|
22
|
+
file=sys.stderr)
|
|
23
|
+
return 2
|
|
24
|
+
out, checked_at, latest, current = argv[1], argv[2], argv[3], argv[4]
|
|
25
|
+
try:
|
|
26
|
+
checked = int(checked_at)
|
|
27
|
+
except ValueError:
|
|
28
|
+
print(f"checked_at is not an integer: {checked_at!r}", file=sys.stderr)
|
|
29
|
+
return 2
|
|
30
|
+
payload: dict[str, object] = {"checked_at": checked}
|
|
31
|
+
if current:
|
|
32
|
+
payload["current"] = current
|
|
33
|
+
if latest:
|
|
34
|
+
payload["latest"] = latest
|
|
35
|
+
directory = os.path.dirname(out) or "."
|
|
36
|
+
os.makedirs(directory, exist_ok=True)
|
|
37
|
+
fd, tmp = tempfile.mkstemp(dir=directory, prefix=".update-check.")
|
|
38
|
+
try:
|
|
39
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
40
|
+
json.dump(payload, handle)
|
|
41
|
+
os.replace(tmp, out)
|
|
42
|
+
except BaseException:
|
|
43
|
+
# A temp file left in the state dir would be swept by nothing.
|
|
44
|
+
try:
|
|
45
|
+
os.unlink(tmp)
|
|
46
|
+
except OSError:
|
|
47
|
+
pass
|
|
48
|
+
raise
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
sys.exit(main(sys.argv))
|
package/install.sh
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# agent-bios status show what is installed and where
|
|
14
14
|
# agent-bios cost the session cost / context meter, from any directory
|
|
15
15
|
# agent-bios update git pull + reinstall (clone), or print the npm update line
|
|
16
|
+
# agent-bios update --check cache the registry's latest version for the TUI badge
|
|
16
17
|
# agent-bios uninstall remove deployed files and the zsh hook
|
|
17
18
|
# agent-bios help
|
|
18
19
|
#
|
|
@@ -95,6 +96,13 @@ CLEANUP_FAILED=0
|
|
|
95
96
|
# treating it as a failure made cmd_install exit 1 and the EXIT trap restore the previous
|
|
96
97
|
# manifest — leaving the new corpus on disk with the record saying the old one was deployed.
|
|
97
98
|
ENTRY_NEEDS_ACTION=0
|
|
99
|
+
# Set when the required corpus-status projection could not be written. Deferred rather than
|
|
100
|
+
# returned on the spot: the projection runs AFTER the corpus is deployed, and returning
|
|
101
|
+
# there would fire the EXIT trap and restore the PREVIOUS manifest — leaving the new corpus
|
|
102
|
+
# on disk with the record naming the old one, which is the split state above. The manifest
|
|
103
|
+
# is completed first so it describes what is really there, and the command then exits
|
|
104
|
+
# non-zero. The files, the record, and the exit code then each say something true.
|
|
105
|
+
PROJECTION_FAILED=0
|
|
98
106
|
# Deployed files uninstall could not back up, so did not delete. Global rather than local
|
|
99
107
|
# because the closing summary must not claim a clean removal that did not happen.
|
|
100
108
|
UNBACKED=0
|
|
@@ -859,12 +867,36 @@ cmd_install() {
|
|
|
859
867
|
# deployed, against this script's own `--dry-run print actions without changing
|
|
860
868
|
# anything` and README's identical sentence. A dry run that edits state is worse than
|
|
861
869
|
# no dry run: it is consulted precisely when the user is unwilling to touch anything.
|
|
870
|
+
#
|
|
871
|
+
# The projection is REQUIRED, not best-effort. It used to be neither: stderr and the
|
|
872
|
+
# exit status were both discarded and every failure printed one guess of a note —
|
|
873
|
+
# "versions.json/ledger missing?" — which was wrong for the failure that actually
|
|
874
|
+
# happened. `compose/corpus-state.py` was not in the npm package at all, so a real
|
|
875
|
+
# npm install rewrote the corpus, updated selection.json and version.json, passed
|
|
876
|
+
# verification, exited 0, and left corpus-status.json stale from a previous
|
|
877
|
+
# deployment. The launcher's corpus panel reads that file, so the machine reported a
|
|
878
|
+
# selection the successful run had not recorded. A command that deploys the launcher
|
|
879
|
+
# and advertises its panel cannot call that a success.
|
|
880
|
+
#
|
|
881
|
+
# The script now ships and degrades honestly in an npm layout (domains real,
|
|
882
|
+
# version/ledger reported unavailable), so the remaining failures are real ones:
|
|
883
|
+
# an unwritable destination, an invalid status, a missing interpreter. Those fail
|
|
884
|
+
# the install, and the reason reaches the operator instead of /dev/null.
|
|
862
885
|
if [ "$DRY_RUN" = 1 ]; then
|
|
863
886
|
info "[dry-run] project corpus-status"
|
|
864
|
-
elif python3 "$REPO/compose/corpus-state.py" project --repo "$REPO" >/dev/null 2>&1; then
|
|
865
|
-
info "corpus-status projected"
|
|
866
887
|
else
|
|
867
|
-
|
|
888
|
+
local projection_log
|
|
889
|
+
projection_log="$(mktemp -t corpus-projection)"
|
|
890
|
+
if python3 "$REPO/compose/corpus-state.py" project --repo "$REPO" >"$projection_log" 2>&1; then
|
|
891
|
+
info "corpus-status projected"
|
|
892
|
+
rm -f "$projection_log"
|
|
893
|
+
else
|
|
894
|
+
log "corpus-status projection FAILED — the launcher's corpus panel would report a"
|
|
895
|
+
log "selection this run did not record. Its own output:"
|
|
896
|
+
sed 's/^/ /' "$projection_log"
|
|
897
|
+
log " full output: $projection_log"
|
|
898
|
+
PROJECTION_FAILED=1
|
|
899
|
+
fi
|
|
868
900
|
fi
|
|
869
901
|
# Deploy/system version marker for the launcher's TUI version line, read from
|
|
870
902
|
# package.json (version + releaseDate) — distinct from the corpus content
|
|
@@ -917,7 +949,13 @@ PY
|
|
|
917
949
|
# restore armed above must not fire.
|
|
918
950
|
INSTALL_COMPLETED=1
|
|
919
951
|
log ""
|
|
920
|
-
|
|
952
|
+
# Withheld when the projection failed: the run below reports INSTALL INCOMPLETE and
|
|
953
|
+
# exits non-zero, and printing "Done." first told the operator both things in the
|
|
954
|
+
# same breath. The manifest is already complete by here, which is the point — the
|
|
955
|
+
# files really are deployed; what failed is the record of WHICH selection they are.
|
|
956
|
+
if [ "$PROJECTION_FAILED" != 1 ]; then
|
|
957
|
+
log "Done. Open a new shell (or: source \"$ZSHRC\") to activate the zero-arg launcher."
|
|
958
|
+
fi
|
|
921
959
|
if [ "${ENTRY_NEEDS_ACTION:-0}" = 1 ]; then
|
|
922
960
|
log ""
|
|
923
961
|
log "ONE STEP LEFT: add this line to $CLAUDE_DIR/CLAUDE.md (yours; we never rewrite it):"
|
|
@@ -928,6 +966,13 @@ PY
|
|
|
928
966
|
# must not become a nonzero exit under set -e.
|
|
929
967
|
{ [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ] && log "Replaced files were backed up under $BACKUP_DIR"; } || true
|
|
930
968
|
prune_backups
|
|
969
|
+
if [ "$PROJECTION_FAILED" = 1 ]; then
|
|
970
|
+
log ""
|
|
971
|
+
log "INSTALL INCOMPLETE: the corpus is deployed and the manifest records it, but the"
|
|
972
|
+
log "corpus-status projection failed above — the launcher's panel would describe a"
|
|
973
|
+
log "state this run did not record. Fix the cause and re-run: agent-bios install"
|
|
974
|
+
exit 1
|
|
975
|
+
fi
|
|
931
976
|
else
|
|
932
977
|
log "VERIFY FAILED after install — see messages above"
|
|
933
978
|
exit 1
|
|
@@ -1417,8 +1462,22 @@ cmd_onboard() {
|
|
|
1417
1462
|
log "ONBOARDING INCOMPLETE: the bundle is installed but not loading — fix the cause above and re-run: agent-bios verify"
|
|
1418
1463
|
exit 1
|
|
1419
1464
|
fi
|
|
1420
|
-
|
|
1421
|
-
|
|
1465
|
+
# The SUCCESS record is strict; the two failure records above stay tolerant. The
|
|
1466
|
+
# asymmetry is the point: a swallowed failure-record rides a run that is already
|
|
1467
|
+
# exiting non-zero and reporting why, while a swallowed success-record is how
|
|
1468
|
+
# onboarding prints a completed summary over a status file that never learned the
|
|
1469
|
+
# selection was applied. That is the state this whole change exists to remove, so it
|
|
1470
|
+
# cannot be the one still guarded by `|| true`.
|
|
1471
|
+
record_log="$(mktemp -t corpus-record-apply)"
|
|
1472
|
+
if ! python3 "$REPO/compose/corpus-state.py" record-apply \
|
|
1473
|
+
--requested "$sel" --outcome applied >"$record_log" 2>&1; then
|
|
1474
|
+
log "ONBOARDING INCOMPLETE: the corpus applied, but recording that outcome failed —"
|
|
1475
|
+
log "the corpus panel would not show this selection as applied. Its own output:"
|
|
1476
|
+
sed 's/^/ /' "$record_log"
|
|
1477
|
+
log " full output: $record_log"
|
|
1478
|
+
exit 1
|
|
1479
|
+
fi
|
|
1480
|
+
rm -f "$record_log"
|
|
1422
1481
|
# The prune is authorized by the canary's proof, and cmd_install ran before the canary existed
|
|
1423
1482
|
# for this bundle rev — so it kept everything. Now that loading is proven, run it for real.
|
|
1424
1483
|
migrate_learnings
|
|
@@ -1613,7 +1672,49 @@ shell_shadow_summary() {
|
|
|
1613
1672
|
fi
|
|
1614
1673
|
}
|
|
1615
1674
|
|
|
1675
|
+
# The update check is a READ of a public version number: it sends the package name
|
|
1676
|
+
# and nothing derived from this machine, which is the same class as provision-venv's
|
|
1677
|
+
# PyPI fetch and the onboard model probe — see ENDPOINTS.md "Zero egress by default",
|
|
1678
|
+
# whose claim is scoped to DATA egress. It delegates to `npm view` rather than naming
|
|
1679
|
+
# a registry, so a private registry, a proxy, and the user's auth all keep working and
|
|
1680
|
+
# no shipped file carries a URL (gates/check-endpoints.py forbids that outright).
|
|
1681
|
+
#
|
|
1682
|
+
# `checked_at` is written even when the lookup FAILS. An offline machine that recorded
|
|
1683
|
+
# nothing would retry on every launch, which is the opposite of once a day.
|
|
1684
|
+
refresh_update_cache() {
|
|
1685
|
+
if [ "${AGENT_BIOS_UPDATE_CHECK:-1}" = "0" ]; then
|
|
1686
|
+
log "update check disabled (AGENT_BIOS_UPDATE_CHECK=0)"
|
|
1687
|
+
return 0
|
|
1688
|
+
fi
|
|
1689
|
+
local name latest now out
|
|
1690
|
+
name="$(json_field "$REPO/package.json" name)" || return 1
|
|
1691
|
+
[ -n "$name" ] || return 1
|
|
1692
|
+
now="$(date +%s)"
|
|
1693
|
+
latest=""
|
|
1694
|
+
if command -v npm >/dev/null 2>&1; then
|
|
1695
|
+
# `|| latest=""` is not defensive noise: this file runs under `set -euo pipefail`,
|
|
1696
|
+
# so an npm that exits non-zero (offline, firewalled, private registry down) makes
|
|
1697
|
+
# the PIPELINE fail and `set -e` abort the function before it can record the
|
|
1698
|
+
# attempt — turning every offline launch into a retry and `update --check` into
|
|
1699
|
+
# exit 1. Caught by I16's failing-registry stub, not by review.
|
|
1700
|
+
latest="$(npm view "$name" version 2>/dev/null | tr -d '[:space:]')" || latest=""
|
|
1701
|
+
fi
|
|
1702
|
+
mkdir -p "$STATE_DIR" || return 1
|
|
1703
|
+
out="$STATE_DIR/update-check.json"
|
|
1704
|
+
# Atomic: a half-written cache read by the launcher is a crash in the TUI.
|
|
1705
|
+
python3 "$REPO/compose/write-update-cache.py" "$out" "$now" "$latest" "$(source_version)" || return 1
|
|
1706
|
+
if [ -n "$latest" ]; then
|
|
1707
|
+
log "update check: latest $latest (installed $(source_version))"
|
|
1708
|
+
else
|
|
1709
|
+
log "update check: could not reach the registry; recorded the attempt"
|
|
1710
|
+
fi
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1616
1713
|
cmd_update() {
|
|
1714
|
+
if [ "${UPDATE_CHECK_ONLY:-0}" = 1 ]; then
|
|
1715
|
+
refresh_update_cache
|
|
1716
|
+
return $?
|
|
1717
|
+
fi
|
|
1617
1718
|
if [ -e "$REPO/.git" ]; then
|
|
1618
1719
|
log "Updating from git..."
|
|
1619
1720
|
run git -C "$REPO" pull --ff-only
|
|
@@ -1641,6 +1742,9 @@ agent-bios — deploy the Claude/Codex instruction SSOT into $HOME (by copy).
|
|
|
1641
1742
|
agent-bios cost the session cost / context meter (session-cost.py), from any
|
|
1642
1743
|
directory: agent-bios cost [--context [--budget N]] <transcript>
|
|
1643
1744
|
agent-bios update git pull + reinstall (clone), or print the npm update line
|
|
1745
|
+
agent-bios update --check ask the registry for the latest version and cache it
|
|
1746
|
+
for the launcher's badge; sends the package name and nothing
|
|
1747
|
+
else, runs at most daily, AGENT_BIOS_UPDATE_CHECK=0 disables it
|
|
1644
1748
|
agent-bios uninstall remove deployed files and the zsh hook
|
|
1645
1749
|
agent-bios help
|
|
1646
1750
|
|
|
@@ -1706,6 +1810,7 @@ DOMAINS_SET=0
|
|
|
1706
1810
|
while [ $# -gt 0 ]; do
|
|
1707
1811
|
case "$1" in
|
|
1708
1812
|
--dry-run) DRY_RUN=1 ;;
|
|
1813
|
+
--check) UPDATE_CHECK_ONLY=1 ;;
|
|
1709
1814
|
--with) shift; WITH="${1:-}"; [ -n "$WITH" ] || { log "--with needs a comma-separated capability list"; exit 2; } ;;
|
|
1710
1815
|
--with=*) WITH="${1#--with=}"; [ -n "$WITH" ] || { log "--with needs a comma-separated capability list"; exit 2; } ;;
|
|
1711
1816
|
--domains) shift; DOMAINS_ARG="${1:-}"; DOMAINS_SET=1; [ -n "$DOMAINS_ARG" ] || { log "--domains needs a comma-separated domain list (use onboard for core-only)"; exit 2; } ;;
|
package/launch/agent-launch.py
CHANGED
|
@@ -16,6 +16,7 @@ import string
|
|
|
16
16
|
import subprocess
|
|
17
17
|
import sys
|
|
18
18
|
import tempfile
|
|
19
|
+
import time
|
|
19
20
|
import unicodedata
|
|
20
21
|
import textwrap
|
|
21
22
|
import tomllib
|
|
@@ -7622,6 +7623,94 @@ VERSION_INFO_PATH = pathlib.Path(
|
|
|
7622
7623
|
)
|
|
7623
7624
|
)
|
|
7624
7625
|
|
|
7626
|
+
UPDATE_CHECK_PATH = pathlib.Path(
|
|
7627
|
+
os.environ.get(
|
|
7628
|
+
"AGENT_BIOS_UPDATE_CHECK_STATE",
|
|
7629
|
+
str(pathlib.Path.home() / ".local/share/agent-bios/update-check.json"),
|
|
7630
|
+
)
|
|
7631
|
+
)
|
|
7632
|
+
|
|
7633
|
+
# Once a day. The launcher never performs the lookup itself — it reads this cache and
|
|
7634
|
+
# at most SPAWNS the installer, detached, to refresh it. Two reasons, both load-bearing:
|
|
7635
|
+
# `npm view` routinely takes seconds and a launcher that blocks on the network is worse
|
|
7636
|
+
# than one that shows a stale badge, and the network operation belongs to the component
|
|
7637
|
+
# that already owns fetch-corpus (ENDPOINTS.md) rather than to the UI.
|
|
7638
|
+
UPDATE_CHECK_INTERVAL_S = 24 * 60 * 60
|
|
7639
|
+
|
|
7640
|
+
|
|
7641
|
+
def _version_tuple(text: str) -> tuple[int, ...] | None:
|
|
7642
|
+
"""Numeric release prefix, or None when it is not one.
|
|
7643
|
+
|
|
7644
|
+
Deliberately refuses anything it does not fully understand rather than guessing:
|
|
7645
|
+
a prerelease like 1.2.3-rc1 returns None, so it is never compared and never
|
|
7646
|
+
announced. Announcing an upgrade to a version the user cannot get is worse than
|
|
7647
|
+
announcing nothing."""
|
|
7648
|
+
parts = text.strip().split(".")
|
|
7649
|
+
if not (2 <= len(parts) <= 4):
|
|
7650
|
+
return None
|
|
7651
|
+
out = []
|
|
7652
|
+
for part in parts:
|
|
7653
|
+
if not part.isdigit():
|
|
7654
|
+
return None
|
|
7655
|
+
out.append(int(part))
|
|
7656
|
+
return tuple(out)
|
|
7657
|
+
|
|
7658
|
+
|
|
7659
|
+
def read_update_cache() -> dict | None:
|
|
7660
|
+
try:
|
|
7661
|
+
data = json.loads(UPDATE_CHECK_PATH.read_text(encoding="utf-8"))
|
|
7662
|
+
except (OSError, ValueError):
|
|
7663
|
+
return None
|
|
7664
|
+
return data if isinstance(data, dict) else None
|
|
7665
|
+
|
|
7666
|
+
|
|
7667
|
+
def update_available(deployed: str | None, cache: dict | None) -> str | None:
|
|
7668
|
+
"""The version to announce, or None.
|
|
7669
|
+
|
|
7670
|
+
None whenever anything is unknown — no cache, a cache whose lookup failed (it
|
|
7671
|
+
carries checked_at but no latest), an unparseable version on either side, or a
|
|
7672
|
+
latest that is not strictly greater. `latest` missing means "asked, no answer",
|
|
7673
|
+
which must not read as "up to date"; it simply says nothing."""
|
|
7674
|
+
if not deployed or not cache:
|
|
7675
|
+
return None
|
|
7676
|
+
latest = cache.get("latest")
|
|
7677
|
+
if not isinstance(latest, str):
|
|
7678
|
+
return None
|
|
7679
|
+
here, there = _version_tuple(deployed), _version_tuple(latest)
|
|
7680
|
+
if here is None or there is None:
|
|
7681
|
+
return None
|
|
7682
|
+
return latest if there > here else None
|
|
7683
|
+
|
|
7684
|
+
|
|
7685
|
+
def update_check_due(cache: dict | None, now: float) -> bool:
|
|
7686
|
+
if os.environ.get("AGENT_BIOS_UPDATE_CHECK") == "0":
|
|
7687
|
+
return False
|
|
7688
|
+
if cache is None:
|
|
7689
|
+
return True
|
|
7690
|
+
checked = cache.get("checked_at")
|
|
7691
|
+
if not isinstance(checked, (int, float)):
|
|
7692
|
+
return True
|
|
7693
|
+
return (now - checked) >= UPDATE_CHECK_INTERVAL_S
|
|
7694
|
+
|
|
7695
|
+
|
|
7696
|
+
def spawn_update_check() -> None:
|
|
7697
|
+
"""Fire and forget. Every failure here is silent BY DESIGN — a background
|
|
7698
|
+
refresh that reported its own problems would interrupt a launch to say something
|
|
7699
|
+
the user did not ask for and cannot act on."""
|
|
7700
|
+
installer = shutil.which("agent-bios")
|
|
7701
|
+
if not installer:
|
|
7702
|
+
return
|
|
7703
|
+
try:
|
|
7704
|
+
subprocess.Popen(
|
|
7705
|
+
[installer, "update", "--check"],
|
|
7706
|
+
stdin=subprocess.DEVNULL,
|
|
7707
|
+
stdout=subprocess.DEVNULL,
|
|
7708
|
+
stderr=subprocess.DEVNULL,
|
|
7709
|
+
start_new_session=True,
|
|
7710
|
+
)
|
|
7711
|
+
except OSError:
|
|
7712
|
+
pass
|
|
7713
|
+
|
|
7625
7714
|
|
|
7626
7715
|
def load_corpus_status() -> dict[str, Any] | None:
|
|
7627
7716
|
"""The corpus status projection, or None when the file is unreadable or is not
|
|
@@ -7653,7 +7742,12 @@ def version_label() -> str | None:
|
|
|
7653
7742
|
if not version:
|
|
7654
7743
|
return None
|
|
7655
7744
|
released = info.get("releaseDate")
|
|
7656
|
-
|
|
7745
|
+
label = f"agent-bios v{version} · {released}" if released else f"agent-bios v{version}"
|
|
7746
|
+
cache = read_update_cache()
|
|
7747
|
+
if update_check_due(cache, time.time()):
|
|
7748
|
+
spawn_update_check()
|
|
7749
|
+
newer = update_available(version, cache)
|
|
7750
|
+
return f"{label} · update v{newer} available" if newer else label
|
|
7657
7751
|
|
|
7658
7752
|
|
|
7659
7753
|
def display_width(text: str) -> int:
|
|
@@ -7723,21 +7817,33 @@ def _corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
|
|
|
7723
7817
|
def row(label: str, value: str) -> str:
|
|
7724
7818
|
return label + " " * max(1, column - display_width(label)) + value
|
|
7725
7819
|
|
|
7820
|
+
# `versions is None` is the projection saying "this install cannot know" — a packaged
|
|
7821
|
+
# install has no author version/ledger registry (compose/corpus-state.py). It is not
|
|
7822
|
+
# `[]`, which would mean a checkout whose registry is genuinely empty, and it must not
|
|
7823
|
+
# render as `0 placed · 0 versions`: a fabricated zero is worse than a blank, because
|
|
7824
|
+
# a reader cannot tell it from a real count. The domain rows below stay real, which is
|
|
7825
|
+
# the whole point of degrading rather than withholding the projection.
|
|
7826
|
+
unavailable = status.get("versions") is None
|
|
7827
|
+
summary = status.get("summary") or {}
|
|
7726
7828
|
current = status.get("current_version", "?")
|
|
7727
7829
|
latest = status.get("latest_version", "?")
|
|
7728
|
-
|
|
7830
|
+
# `str(current)` printed the literal "None" on a checkout whose registry exists but
|
|
7831
|
+
# holds no version — pre-existing, and visible the moment the row above it learned to
|
|
7832
|
+
# say "unavailable". A known absence reads as none; an unknown one says so.
|
|
7833
|
+
head = row(labels[0], t("panel.unavailable") if unavailable
|
|
7834
|
+
else str(current) if current else t("panel.layers.none"))
|
|
7729
7835
|
if status.get("rolled_back_to"):
|
|
7730
7836
|
head += " " + t("panel.rolled-back").format(latest=latest)
|
|
7731
|
-
layers =
|
|
7837
|
+
layers = summary.get("placed_by_layer", {})
|
|
7732
7838
|
order = ("global", "guide", "hook", "enforcement", "gate")
|
|
7733
7839
|
layer_text = " · ".join(
|
|
7734
7840
|
f"{name} {layers[name]}" for name in order if layers.get(name)
|
|
7735
7841
|
) or t("panel.layers.none")
|
|
7736
|
-
by_status =
|
|
7842
|
+
by_status = summary.get("by_status", {})
|
|
7737
7843
|
lines = [
|
|
7738
7844
|
head,
|
|
7739
|
-
row(labels[1], layer_text),
|
|
7740
|
-
row(labels[2], t("panel.ledger.value").format(
|
|
7845
|
+
row(labels[1], t("panel.unavailable") if unavailable else layer_text),
|
|
7846
|
+
row(labels[2], t("panel.unavailable") if unavailable else t("panel.ledger.value").format(
|
|
7741
7847
|
placed=by_status.get("placed", 0),
|
|
7742
7848
|
incubating=by_status.get("incubating", 0) + by_status.get("incubating-G", 0),
|
|
7743
7849
|
versions=len(status_list(status.get("versions"))),
|
package/launch/i18n/en.toml
CHANGED
|
@@ -169,6 +169,7 @@
|
|
|
169
169
|
"panel.domains.label" = "Domains"
|
|
170
170
|
"panel.rolled-back" = "(ROLLED BACK; latest is {latest})"
|
|
171
171
|
"panel.layers.none" = "none"
|
|
172
|
+
"panel.unavailable" = "unavailable in a packaged install"
|
|
172
173
|
"panel.ledger.value" = "placed {placed} · incubating {incubating} · versions {versions}"
|
|
173
174
|
"panel.domains.unset" = "(none selected yet)"
|
|
174
175
|
"panel.last-apply" = "⚠ LAST APPLY {outcome} at {at} — requested: {requested}"
|
package/launch/i18n/ja.toml
CHANGED
|
@@ -165,6 +165,7 @@
|
|
|
165
165
|
"panel.domains.label" = "ドメイン"
|
|
166
166
|
"panel.rolled-back" = "(ロールバック済み。最新は {latest})"
|
|
167
167
|
"panel.layers.none" = "なし"
|
|
168
|
+
"panel.unavailable" = "パッケージ導入では取得不可"
|
|
168
169
|
"panel.ledger.value" = "配置 {placed} · インキュベート {incubating} · バージョン {versions}"
|
|
169
170
|
"panel.domains.unset" = "(未選択)"
|
|
170
171
|
"panel.last-apply" = "⚠ 最終適用 {outcome} @ {at} — 要求: {requested}"
|
package/launch/i18n/ko.toml
CHANGED
|
@@ -165,6 +165,7 @@
|
|
|
165
165
|
"panel.domains.label" = "도메인"
|
|
166
166
|
"panel.rolled-back" = "(롤백됨. 최신은 {latest})"
|
|
167
167
|
"panel.layers.none" = "없음"
|
|
168
|
+
"panel.unavailable" = "패키지 설치에서는 확인 불가"
|
|
168
169
|
"panel.ledger.value" = "배치 {placed} · 인큐베이팅 {incubating} · 버전 {versions}"
|
|
169
170
|
"panel.domains.unset" = "(아직 선택 안 함)"
|
|
170
171
|
"panel.last-apply" = "⚠ 마지막 적용 {outcome} @ {at} — 요청: {requested}"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-bios",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"releaseDate": "2026-
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"releaseDate": "2026-09-01",
|
|
5
5
|
"description": "A thin, low-level instruction layer for LLM CLI agents: one set of principles and behavior whichever model you run. Deploys into $HOME by copy via an explicit `agent-bios install`.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"agent-bios": "install.sh"
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"compose/assemble.py",
|
|
30
30
|
"compose/prune-backups.py",
|
|
31
31
|
"compose/check-domains.py",
|
|
32
|
+
"compose/corpus-state.py",
|
|
33
|
+
"compose/write-update-cache.py",
|
|
32
34
|
"compose/canary.sh",
|
|
33
35
|
"launch/check-prompting-targets.sh",
|
|
34
36
|
"learn/check-learning.py",
|
package/provenance.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"commit":"
|
|
1
|
+
{"commit":"ff09dd50ec264eee660513f8a6768d3481a4e9c8","committedAt":"2026-09-01T14:37:00+09:00","dirty":false}
|