Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bc77f80
ci(windows): a feed outage must not be reported as this change's red …
fujibee Aug 15, 2026
9bbb7a7
PROBE (reverted next commit): point the pin at a version the feed can…
fujibee Aug 15, 2026
8b0072d
Revert "PROBE (reverted next commit): point the pin at a version the …
fujibee Aug 15, 2026
29b9765
PROBE 2 (reverted next commit): the composed control, on top of #822
fujibee Aug 15, 2026
8e0a425
PROBE 2b (reverted next commit): re-run against the current merge ref
fujibee Aug 15, 2026
b0b5a1a
fix: the guard was on the wrong job's step, and it silenced a real one
fujibee Aug 15, 2026
31156bb
Revert the probe: the composed control is measured, on both legs
fujibee Aug 15, 2026
4c448cb
Merge pull request #827 from fujibee/fix/824-a-feed-outage-must-not-l…
fujibee Aug 16, 2026
9dc668e
Stop telling every agent that key rotate is unavailable (#841)
fujibee Aug 16, 2026
121d27b
fix(install): record provenance when git answers in another path spac…
fujibee Aug 16, 2026
f7de22a
fix(roster): bound the wait on the local child without spawning anyth…
fujibee Aug 17, 2026
3259d58
Windows can use the hosted service: connect reaches the server, the f…
fujibee Aug 18, 2026
d6b741b
release: 1.2.1 (#872)
fujibee Aug 18, 2026
67a1252
docs(contributing): say that main takes squash commits only (#845)
fujibee Aug 18, 2026
626a625
fix(watch): fold sentinel '-' into the empty session-id path (#857)
apstndb Aug 18, 2026
df4c597
fix(resolve): map grok-build to grok in the pid walk (#860)
apstndb Aug 18, 2026
fa826b7
fix(compat): take the basename of the whole ps comm path (#770) (#771)
cattyneo Aug 18, 2026
2910722
fix(install): derive --update's type re-detection from the same list …
fujibee Aug 18, 2026
076705f
Merge upstream/main (fujibee/agmsg) into fork main
kappaseijin Aug 19, 2026
e0b4f9b
fix(compat): make the space-containing-path check enforceable
kappaseijin Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "agmsg",
"description": "Cross-agent messaging via SQLite. Send messages between CLI AI agents. No daemon, no network.",
"version": "1.2.0",
"version": "1.2.1",
"author": {
"name": "fujibee"
},
Expand Down
112 changes: 107 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ env:
# Number of parallel bats shards per OS. The matrix and the shard helper must
# stay in lockstep; the stable summary job below verifies that they do.
SHARD_TOTAL: 4
# The Windows legs' sqlite3, pinned three ways (#824).
#
# This comment used to end "one trip to chocolatey, which is the only time
# the community feed is asked at all". There is no feed any more: the binary
# comes from one sqlite.org URL, and the hash is what makes the pin mean
# something rather than the version string alone.
#
# Bumping a release means changing all four together — YEAR and BUILD form
# the URL, VERSION is what `sqlite3 --version` will report, and SHA256 is the
# measured digest of that exact zip. A stale SHA256 fails the download step
# loudly, which is the intended way to find out one of them was missed.
WINDOWS_SQLITE_VERSION: '3.53.4'
WINDOWS_SQLITE_YEAR: '2026'
WINDOWS_SQLITE_BUILD: '3530400'
WINDOWS_SQLITE_SHA256: 'f46ee2475de4cbe287e6e5f7d43c838796b14e7379cd216bdbb28d391429f9fc'

jobs:
# Cheap gate: is this PR's diff entirely documentation? Pushes to main always
Expand Down Expand Up @@ -575,14 +590,85 @@ jobs:
if: needs.changes.outputs.docs_only == 'true'
run: echo "Diff is documentation-only; skipping the Windows bats legs."

- name: Install sqlite3
# NO PACKAGE FEED ON THESE LEGS AT ALL (#824).
#
# This used to be `choco install sqlite`. On 2026-08-15 the chocolatey
# community feed answered 503/504 for hours and every Windows leg went
# red for a reason that had nothing to do with the change under test —
# and worse, WHICH step went red depended on which phase of chocolatey's
# two-phase resolution happened to fail, so the same outage was not
# reproducible in place. Measured: `installed 0/1` exits non-zero and
# reddens the install; `installed 0/0` exits ZERO and the red lands three
# steps later on `Run tests`, reading as the diff's fault.
#
# #827 made that legible — retry, cache, and a presence check that is the
# only step allowed to be red about it. This removes the cause instead.
# The issue's own Directions ranked the retry last, "the cheapest change
# and the least durable", and it was right: on a cache miss the leg still
# needed the feed. `bats` is a required check on `main` with
# enforce_admins, so a red Windows leg now blocks every landing.
#
# What is gone is the FEED, not the third party: sqlite.org is still
# somebody else's host. What it is not is a package index — no resolver,
# no two-phase lookup, no per-request 503 that serves one concurrent job
# and refuses another. One pinned URL, and a hash that says the bytes are
# the ones this pin was measured against.
#
# Not measured: whether the GitHub Windows image already ships sqlite3.
# It does not appear in the Windows 2022 or 2025 image manifests, which
# is the published list rather than a `where sqlite3` on a live runner.
- name: Restore sqlite3 (cache)
if: needs.changes.outputs.docs_only != 'true' && matrix.sqlite
id: sqlite-cache
uses: actions/cache@v4
with:
path: C:\sqlite-tools
key: sqlite3-${{ runner.os }}-sqliteorg-${{ env.WINDOWS_SQLITE_VERSION }}

- name: Fetch sqlite3 from sqlite.org
if: needs.changes.outputs.docs_only != 'true' && matrix.sqlite && steps.sqlite-cache.outputs.cache-hit != 'true'
shell: pwsh
run: choco install sqlite -y --no-progress
run: |
$ErrorActionPreference = 'Stop'
$url = "https://sqlite.org/$env:WINDOWS_SQLITE_YEAR/sqlite-tools-win-x64-$env:WINDOWS_SQLITE_BUILD.zip"
$zip = Join-Path $env:RUNNER_TEMP 'sqlite-tools.zip'
# Three attempts: one host having a bad second is still possible, it
# is just no longer a package index having a bad phase.
for ($attempt = 1; $attempt -le 3; $attempt++) {
try {
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
break
} catch {
Write-Host "::warning::sqlite.org fetch failed on attempt $attempt"
Start-Sleep -Seconds (10 * $attempt)
}
}
if (-not (Test-Path $zip)) { exit 0 } # the presence check below decides the colour
# THE HASH IS THE POINT. Without it this trades a feed that answers
# errors for a host that could answer anything, and the tests would
# run against whatever arrived.
$actual = (Get-FileHash -Path $zip -Algorithm SHA256).Hash.ToLower()
if ($actual -ne $env:WINDOWS_SQLITE_SHA256) {
Write-Host "::error::sqlite-tools zip hash mismatch. expected $env:WINDOWS_SQLITE_SHA256, got $actual"
exit 1
}
Expand-Archive -Path $zip -DestinationPath 'C:\sqlite-tools' -Force

- name: Put chocolatey shims on PATH (Git Bash form)
- name: Put sqlite3 on PATH (Git Bash form)
if: needs.changes.outputs.docs_only != 'true' && matrix.sqlite
run: echo "/c/ProgramData/chocolatey/bin" >> "$GITHUB_PATH"
run: echo "/c/sqlite-tools" >> "$GITHUB_PATH"

# THE STEP THAT IS ALLOWED TO BE RED ABOUT THIS, kept from #827 and still
# the load-bearing part: whatever supplies sqlite3, the step that decides
# the leg's colour must be the one that says the dependency is missing,
# not the one that runs the tests.
- name: sqlite3 must be present, or this leg is about its download
if: needs.changes.outputs.docs_only != 'true' && matrix.sqlite
run: |
if ! sqlite3 --version; then
echo "::error::sqlite3 is missing — the pinned sqlite.org download did not arrive. This leg reports on that, NOT on the change under test — see #824."
exit 1
fi

- name: Install bats
if: needs.changes.outputs.docs_only != 'true'
Expand All @@ -595,7 +681,23 @@ jobs:
if: needs.changes.outputs.docs_only != 'true'
run: |
bash --version | head -1
sqlite3 --version || sqlite3.exe --version || echo "sqlite3 not found on PATH"
# Two changes here, pulling in opposite directions (#824).
#
# The `|| sqlite3.exe --version || echo "sqlite3 not found on PATH"`
# fallback is gone: it turned a missing binary into a success line, so
# nothing between a failed install and `Run tests` said anything was
# wrong. Absence is decided by the presence check above, which is
# allowed to be red about it.
#
# Without the fallback this line would fail on a leg that never
# installs sqlite3 — `driver input (#817)` carries `sqlite: false`
# precisely so a package feed cannot stop a leg driving a mock bash
# driver. Reporting a version it was never meant to have would
# re-create, one step lower, the coupling #822 removed.
# `matrix.sqlite` is a fixed value from the matrix above.
if [ "${{ matrix.sqlite }}" = "true" ]; then
sqlite3 --version
fi
bats --version

- name: Run tests
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.1] - 2026-08-18

### Added
- Windows can use the hosted service: connect reaches the server, the fingerprint is real, and doctor names a wedged lock (#868)

### Fixed
- Bound the wait on the local child without spawning anything to do it (#821)
- Record provenance when git answers in another path space (#830) (#842)
- Stop telling every agent that key rotate is unavailable (#841)
- The guard was on the wrong job's step, and it silenced a real one

## [1.2.0] - 2026-08-15

### Fixed
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
3. テストスイートを実行する: `bats tests/`。
4. 周囲のコードスタイルに合わせる。Bash が主要言語であり、すべてのスクリプトの先頭で `set -euo pipefail` を使うこと。
5. ユーザーから見える変更であれば、ドキュメントも更新する。
6. PR は `main` に squash コミット 1 つとして着地する — リポジトリ側で merge と rebase のマージは無効にしてある。ブランチのコミットは 1 つにまとめられるので、PR のタイトルはそのままコミットの件名として読める形で書くこと。レビュー前にブランチの履歴を整える必要はない。

## リリース

Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Open an issue on [`fujibee/agmsg`](https://github.com/fujibee/agmsg/issues). Inc
3. Run the test suite: `bats tests/`.
4. Match the surrounding code style. Bash is the primary language; use `set -euo pipefail` at the top of every script.
5. Update docs if the change is user-visible.
6. PRs land on `main` as a single squash commit — merge and rebase merges are disabled on the repository. Your branch's commits are collapsed into one, so write the PR title to read well as that commit's subject. There is no need to tidy your branch history before review.

## Releases

Expand Down
20 changes: 18 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,24 @@ If argument starts with "key import" followed by a team name:
3. Do not offer an environment-variable path. An identity file is a permanent
secret; always use the human-in-own-terminal flow above.

`key rotate` and device-pairing `key request`/`key approve` are not available
yet. If the user asks for one, tell them so instead of attempting to run it.
If argument starts with "key rotate" followed by a team name:
1. Rotation mints a replacement epoch for a team that already has a key and
announces it on the roster journal. It requires an existing current key, an
identity journal (connect or migrate the team first), and `age`; it refuses
with a message naming whichever is missing.
2. Confirm with the user before running it. It changes the team's key state,
and every other machine has to receive the new identity out of band.
3. Run: `bash ~/.agents/skills/agmsg/scripts/key.sh rotate <team>`
4. Show the output: epoch, key_id, and recipient fingerprint. The private key
is never written to the journal. Revealing it needs
`key show <team> --key-id <id> --reveal-secret`, which is refused in agent
mode — tell the user to run that in their own terminal.
5. Messages before the acknowledged rotation boundary remain readable with the
old key.

Device pairing (`key request` / `key approve`) is not implemented — they are
not `key.sh` subcommands, so a call prints usage and exits 1. If the user asks
for one, tell them so instead of attempting to run it.

## Permission prompts (Claude Code)

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.2.0
1.2.1
1 change: 1 addition & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ commit_parsers = [
{ message = "^Fix Codex monitor multi-identity delivery", group = "<!-- 1 -->Fixed" },
{ message = "^Isolate Codex monitor bridges by role", group = "<!-- 0 -->Added" },
{ message = "^Allow launcher-reserved bridge PID", group = "<!-- 1 -->Fixed" },
{ message = "^Stop telling every agent that key rotate", group = "<!-- 1 -->Fixed" },
# Everything else (merges, initial commit, misc) is intentionally dropped.
{ message = ".*", skip = true },
]
Expand Down
83 changes: 64 additions & 19 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ AGENTS_DIR="$HOME/.agents"
# uncommitted changes. Non-git (tarball via setup.sh/npx, no .git): fall back to
# the canonical VERSION file. See #117.
agmsg_source_version() {
local v top
local v top native
# Only describe when SCRIPT_DIR is ITS OWN git checkout. `git describe`
# searches ancestors for a .git, so a non-git copy unpacked under some other
# git repo would otherwise record that PARENT repo's describe instead of
Expand All @@ -51,7 +51,34 @@ agmsg_source_version() {
# app's own version comparison (agmsg_core_version_status in agmsg.rs)
# can't parse as semver, which it then treats as "outdated" unconditionally.
top="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
if [ -n "$top" ] && [ "$top" = "$SCRIPT_DIR" ] \
# THE TWO SIDES ARE IN DIFFERENT PATH SPACES ON WINDOWS, so the equality was
# always false there and every Git Bash install recorded the VERSION file
# instead of the describe string (#830):
#
# $SCRIPT_DIR /tmp/tmp.XXXX/agmsg MSYS form, from bash
# git --show-toplevel C:/Users/.../tmp.XXXX/agmsg native form, from git
#
# `cygpath -m` is the mixed form git reports — the same second chance this
# file already takes for the writable paths below, and the same one
# `agmsg_cmdline_names_path` takes in compat.sh, where the identical mismatch
# made four watcher-ownership checks answer "not ours" on Windows.
#
# The condition below is a CAPABILITY, not an operating system: where cygpath
# is not on PATH, `native` stays empty and this is the plain comparison and
# nothing else. Saying "off Windows" instead would be wider than the code —
# this file's own test drives the second branch on macOS and Linux by putting
# a cygpath stub on PATH.
#
# Where cygpath is absent, fails, returns nothing, or returns a path unequal
# to git's toplevel, the recorded value is the fallback, exactly as before.
# A wrong answer that happened to equal the toplevel would still take the
# describe branch, so this is a set of conditions and not a guarantee that
# the worst case is the old behaviour.
native=""
if command -v cygpath >/dev/null 2>&1; then
native="$(cygpath -m "$SCRIPT_DIR" 2>/dev/null || true)"
fi
if [ -n "$top" ] && { [ "$top" = "$SCRIPT_DIR" ] || { [ -n "$native" ] && [ "$top" = "$native" ]; }; } \
&& v="$(git -C "$SCRIPT_DIR" describe --tags --always --dirty --abbrev=7 --match 'v[0-9]*' 2>/dev/null)" \
&& [ -n "$v" ]; then
printf '%s' "$v"
Expand All @@ -68,6 +95,18 @@ UPDATE_ONLY=false
INTERACTIVE=true
AGENT_TYPE="" # claude-code, codex, gemini, antigravity — passed via --agent-type, or empty for auto/default

# Types the installer renders their OWN shared SKILL.md for (their template.md
# differs from codex's). Everything else -- codex itself, plus claude-code and
# copilot, which keep separate dedicated copies elsewhere -- gets the codex-
# typed shared SKILL.md. One list, read by three call sites below (fresh
# install's template pick, --update's template pick, and --update's type
# re-detection from the SKILL.md already on disk): before #846, the third site
# hardcoded its own, narrower copy of this same set (missing opencode/hermes/
# cursor) that had already drifted from the other two -- re-detecting one of
# those three types as "codex" and then, via the template pick, overwriting
# the SKILL.md the installer itself had written with the wrong flavor.
AGMSG_SHARED_SKILL_TPL_TYPES="gemini antigravity opencode hermes cursor grok-build"

configure_codex_sandbox() {
# --- Configure Codex sandbox (if Codex is installed) ---
# The Codex bridge writes pidfiles/sockets/request files under the
Expand Down Expand Up @@ -380,22 +419,28 @@ if [ "$UPDATE_ONLY" = true ]; then
CMD_NAME="$SKILL_NAME"
echo " Updating $SKILL_NAME..."
if [ -z "$AGENT_TYPE" ]; then
if grep -q "whoami.sh.*antigravity" "$SKILL_DIR/SKILL.md" 2>/dev/null; then
AGENT_TYPE="antigravity"
elif grep -q "whoami.sh.*gemini" "$SKILL_DIR/SKILL.md" 2>/dev/null; then
AGENT_TYPE="gemini"
elif grep -q "whoami.sh.*grok-build" "$SKILL_DIR/SKILL.md" 2>/dev/null; then
AGENT_TYPE="grok-build"
else
AGENT_TYPE="codex"
fi
# Re-detect the type this install's shared SKILL.md was last rendered for,
# from the whoami.sh line its own template prints (#846) -- every
# renderable type's line is unambiguous against every other's; see the
# cross-grep this list is built from, noted alongside
# AGMSG_SHARED_SKILL_TPL_TYPES above. codex is not grepped for: it is the
# default a match against this list falls back to.
AGENT_TYPE="codex"
for _agmsg_t in $AGMSG_SHARED_SKILL_TPL_TYPES; do
if grep -q "whoami.sh.*$_agmsg_t" "$SKILL_DIR/SKILL.md" 2>/dev/null; then
AGENT_TYPE="$_agmsg_t"
break
fi
done
unset _agmsg_t
fi
# The shared SKILL.md uses the codex template by default; gemini/antigravity/
# opencode get their own. (claude-code and copilot reuse the codex-typed
# shared SKILL.md; their dedicated copies are dropped separately below.)
# The shared SKILL.md uses the codex template by default; the types in
# AGMSG_SHARED_SKILL_TPL_TYPES get their own. (claude-code and copilot reuse
# the codex-typed shared SKILL.md; their dedicated copies are dropped
# separately below.)
TPL_TYPE="codex"
case "$AGENT_TYPE" in
gemini|antigravity|opencode|hermes|cursor|grok-build) TPL_TYPE="$AGENT_TYPE" ;;
case " $AGMSG_SHARED_SKILL_TPL_TYPES " in
*" $AGENT_TYPE "*) TPL_TYPE="$AGENT_TYPE" ;;
esac
sed "s/__SKILL_NAME__/$SKILL_NAME/g" "$(agmsg_type_template_path "$TPL_TYPE")" > "$SKILL_DIR/SKILL.md"
# Recursive sync so nested helper dirs (scripts/lib/, scripts/drivers/types/)
Expand Down Expand Up @@ -536,10 +581,10 @@ mkdir -p "$SKILL_DIR"/{scripts,types,db,agents}

# SKILL.md is generated from the agent-specific command template, resolved from
# the type manifest (scripts/drivers/types/<type>/template.md). The shared SKILL.md uses the
# codex template by default; gemini/antigravity/opencode get their own.
# codex template by default; the types in AGMSG_SHARED_SKILL_TPL_TYPES get their own.
TPL_TYPE="codex"
case "$AGENT_TYPE" in
gemini|antigravity|opencode|hermes|cursor|grok-build) TPL_TYPE="$AGENT_TYPE" ;;
case " $AGMSG_SHARED_SKILL_TPL_TYPES " in
*" $AGENT_TYPE "*) TPL_TYPE="$AGENT_TYPE" ;;
esac
sed "s/__SKILL_NAME__/$CMD_NAME/g" "$(agmsg_type_template_path "$TPL_TYPE")" > "$SKILL_DIR/SKILL.md"
# Recursive sync so nested helper dirs (scripts/lib/, scripts/drivers/types/) ship
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agmsg",
"version": "1.2.0",
"version": "1.2.1",
"description": "Cross-agent messaging via SQLite for CLI AI agents (Claude Code, Codex, Gemini CLI, GitHub Copilot CLI, Antigravity, OpenCode). The npm package is a thin bootstrapper that fetches and runs the canonical bash installer at https://github.com/fujibee/agmsg.",
"bin": {
"agmsg": "bin/agmsg.js"
Expand Down
Loading
Loading