Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
5953e9b
fix(bin): keep a declared wait on the pause cadence under a busy pane…
3264studios Aug 27, 2026
10b93b2
fix(bin): recover Claude auto-arm from hung claims (#3156)
kunchenguid Aug 27, 2026
7ee0c19
fix(bin): verify the real GitHub merge outcome instead of reporting a…
wjkawecki-jt Aug 27, 2026
4f89f5b
fix(pi): prevent duplicate captain outcome reports (#3184)
kunchenguid Aug 27, 2026
bca584a
fix(bin): prioritize active pipeline-owned crew runs (#3194)
kunchenguid Aug 27, 2026
c651b59
fix(pi): surface requested outcomes without replaying fleet events (#…
kunchenguid Aug 28, 2026
1fd7ea2
feat(bin): add concurrent bounded remote transport lanes (#3210)
kunchenguid Aug 28, 2026
4207214
fix(bin): accelerate and bound changed test runs (#3250)
kunchenguid Aug 28, 2026
a390659
feat(bin): publish per-home summary ledgers (#3222)
kunchenguid Aug 29, 2026
52b59a1
fix(pi): gate first provider call on startup context (#3158)
M00NLIG7 Aug 29, 2026
f66be0f
fix(pi): restore Pi 0.84.4 renderer compatibility (#3261)
stanzhang Aug 29, 2026
5f31097
fix(bin): keep home-summary publication from starving supervision (#3…
kunchenguid Aug 29, 2026
4eb587d
fix(bin): prevent routine updates from hiding actionable status (#3268)
kunchenguid Aug 29, 2026
c731c36
docs(skills): split harness adapter operations reference (#3289)
M00NLIG7 Aug 29, 2026
0ace60a
test: centralize shared shell fixtures (#3296)
kunchenguid Aug 29, 2026
9e3df47
refactor: retire legacy PR-check migration machinery (#3299)
kunchenguid Aug 29, 2026
1fbc7bb
feat(bin): add trusted process-event extension bindings (#3247)
M00NLIG7 Aug 29, 2026
c7fdef9
fix(bin): deliver safety rules to promoted workers (#3269)
kunchenguid Aug 30, 2026
debe4bf
fix(bin): present Lavish feedback as structured output (#3321)
kunchenguid Aug 30, 2026
1260adc
fix: keep task records and backlog transitions atomic (#3322)
kunchenguid Aug 30, 2026
d71f4b9
fix(bin): contain promote and Relay metadata publishing (#3342)
kunchenguid Aug 30, 2026
a56a78a
fix(bin): absorb turn-end wakes during bounded pane churn (#2877)
karotkriss Aug 30, 2026
0866a77
fix(bin): safely unregister custom checks (#3369)
kunchenguid Aug 31, 2026
4ad8cba
refactor(quota): extract mid-task polling and candidate selection int…
0x7067 Aug 31, 2026
6c1d2db
fix: surface comments on Lavish annotations (#3371)
kunchenguid Aug 31, 2026
a5f3cbe
fix: support first public-followup registration on Bash 3.2 (#3420)
kunchenguid Sep 1, 2026
355f46f
fix(bin): isolate new Herdr server environments (#2792)
RooseveltAdvisors Sep 1, 2026
41d0ab3
fix: surface inbound Relay media to responding agents (#3442)
kunchenguid Sep 1, 2026
f2ee922
fix(bin): defer inactive reconciliation during startup (#3480)
kunchenguid Sep 2, 2026
f42a629
fix(bin): bound wake drain presentation lock waits (#3475)
kunchenguid Sep 2, 2026
ee58e39
fix(bin): retire public follow-ups in remote homes (#3479)
kunchenguid Sep 2, 2026
7d4b517
fix(bin): support process events under symlinked homes (#3484)
kunchenguid Sep 2, 2026
5466394
fix(pi): deliver captain outcomes as deterministic transcript entries…
FocalFactotum Sep 2, 2026
3b891c8
feat: add bounded concurrent Bearings ledger collection (#3481)
kunchenguid Sep 2, 2026
1459c4d
ci: rebalance portable serial test shards (#3489)
kunchenguid Sep 2, 2026
714da64
fix(pi): fall back on incomplete supervision branch prompts (#3491)
kunchenguid Sep 2, 2026
1c41029
fix(pi): re-probe supervision branch after cooldown (#3497)
kunchenguid Sep 2, 2026
521de54
fix(bin): remove legacy remote snapshot reads (#3501)
kunchenguid Sep 2, 2026
84c01b4
fix(pi): preserve watcher continuity across session replacement (#3498)
kunchenguid Sep 2, 2026
d977128
fix(bin): resurface task statuses missed by wake handling (#3495)
kunchenguid Sep 2, 2026
56b4c15
fix(bin): collect follow-up results from remote work homes (#3503)
kunchenguid Sep 2, 2026
763f597
fix(bin): exclude secondmates from home-summary validity (#3504)
kunchenguid Sep 2, 2026
88fb3c0
fix(bin): self-heal outcome indexes on first drain (#3509)
kunchenguid Sep 2, 2026
8988af2
fix(bearings): keep active children underway during captain holds (#3…
kunchenguid Sep 2, 2026
77ee3c8
fix(pi): settle watcher delivery on Pi accepting the follow-up (#3513)
kunchenguid Sep 2, 2026
d22318e
fix(bin): bound repeat stale wakes for parked workers (#3532)
mremond Sep 2, 2026
5fb0ce7
fix(bin): accept the away-mode daemon as the turn-end supervision own…
krakns Sep 2, 2026
353a8f0
fix(backlog): omit --file from row probes for non-markdown backends (…
RooseveltAdvisors Sep 3, 2026
1c00e86
fix(bin): classify progress updates on requested work as routine (#3589)
kunchenguid Sep 3, 2026
b2e3e9e
fix(bin): preserve captain calls during teardown (#3595)
kunchenguid Sep 3, 2026
d3fcdfa
fix(bin): deliver secondmate outcomes to the parent channel (#3592)
kunchenguid Sep 3, 2026
e1d1d69
fix(bin): sync remote second mates to primary commit (#3599)
kunchenguid Sep 3, 2026
28fb5ac
fix(bin): separate captain intent from firstmate specs (#3597)
kunchenguid Sep 3, 2026
3d2a08b
fix: start a fresh supervision branch for every main session (#3600)
kunchenguid Sep 3, 2026
1c5c9c1
feat: restart second mates after instruction updates (#3614)
kunchenguid Sep 3, 2026
7dcf072
perf: accelerate local validation with bounded concurrency (#3644)
kunchenguid Sep 3, 2026
75b2de2
fix: copy PR URLs from durable records (#3648)
kunchenguid Sep 3, 2026
3034912
fix(bin): disable Claude feedback drafts for fleet launches (#3661)
kunchenguid Sep 3, 2026
7adb358
feat(tests): run three more validation families concurrently (#3662)
kunchenguid Sep 3, 2026
ed44d15
feat: structure no-mistakes ask-user escalations (#3670)
kunchenguid Sep 3, 2026
3b82ebd
fix(bin): require self-sufficient no-mistakes intent (#3671)
kunchenguid Sep 3, 2026
5a7ba57
fix: accelerate local Bearings snapshot composition (#3499)
kunchenguid Sep 4, 2026
f4d7875
fix: prevent stale supervision wake loops (#3672)
kunchenguid Sep 4, 2026
31cba0d
fix(bin): avoid fleet snapshot argument limits (#3677)
haroarthur Sep 4, 2026
b82d09f
fix(bin): attribute active runs with unfetched pipeline heads (#3681)
npayette84 Sep 4, 2026
1b0fbb9
fix(bin): pre-register claude workspace trust at spawn time (#3663)
RooseveltAdvisors Sep 4, 2026
efbeb4f
fix: restart every live second mate after updates (#3690)
kunchenguid Sep 4, 2026
3c1e86d
fix(bin): close pending-reply decisions via resolve-key (#3696)
kunchenguid Sep 4, 2026
d43e610
fix(bin): prevent false missed-reply escalations (#3697)
kunchenguid Sep 4, 2026
a5c64a0
feat: add verified Gemini crewmate runtime (#3695)
att430 Sep 4, 2026
8f7b79c
fix(teardown): conclude parked runs advanced past task copy (#3704)
npayette84 Sep 4, 2026
86ff1bf
fix: classify captain holds from structured state (#3508)
kunchenguid Sep 5, 2026
f09de8a
fix(pi): keep supervision outcome delivery responsive (#3767)
kunchenguid Sep 5, 2026
1820316
fix: avoid duplicate AGENTS.md governance for marked projects (#3763)
tiago-peixoto Sep 5, 2026
64304b6
fix: protect primary checkout when spawning from linked homes (#3783)
tiago-peixoto Sep 5, 2026
e075c96
fix(bin): stop reading an unanswered backend probe as a dead endpoint…
RooseveltAdvisors Sep 5, 2026
8fd5575
fix(bin): preserve subshell lock ownership on Bash 3.2 (#3789)
tiago-peixoto Sep 5, 2026
af8c4b6
fix(bin): resolve captain holds and legacy teardowns on non-markdown …
RooseveltAdvisors Sep 5, 2026
a29cdce
fix: reduce local ShellCheck source-analysis cost (#3778)
kunchenguid Sep 5, 2026
d6660d7
fix(pi): route decision-owned wake batches to main (#3776)
kunchenguid Sep 5, 2026
f19efa6
feat(bin): add opt-in worker launch environment allowlist (#3802)
tiago-peixoto Sep 5, 2026
f9aca25
feat(bin): add rovo crewmate/scout adapter with home-path file access…
Puneet-Patwari Sep 5, 2026
12fc10e
fix(bin): prevent false pipeline blocks after drive timeouts (#3813)
kunchenguid Sep 6, 2026
702004e
fix(bin): scope the worker role contract for ship and scout launches …
tiago-peixoto Sep 6, 2026
c499f84
fix(bin): stop ringing steering doorbells into dead panes (#3823)
kunchenguid Sep 6, 2026
51d2e8c
fix(bin): wait out transient primary-checkout reads in the spawn work…
tiago-peixoto Sep 6, 2026
c562be1
fix: support stock macOS Bash 3.2 paths (#3732)
3264studios Sep 6, 2026
a913539
fix(bin): read orphaned green ci monitor and daemon-down failed recor…
RooseveltAdvisors Sep 6, 2026
85ad5e7
fix(spawn): verify preserved backlog state after interrupted spawn de…
RooseveltAdvisors Sep 6, 2026
f3b7e74
docs: correct runtime-backend maturity labels for Herdr (#3821)
kmorebetter Sep 6, 2026
f91a950
fix: restore intent-targeted no-mistakes validation (#3865)
kunchenguid Sep 6, 2026
b028e8b
fix: verify Treehouse slot ownership before teardown (#3837)
kunchenguid Sep 6, 2026
2e65d2e
feat(bin): add verified omp (Oh My Pi) harness adapter for crew, seco…
danielkuykendall23-boop Sep 6, 2026
d8e2bb3
fix(pi): invoke Bash helpers correctly on native Windows (#3843)
cr101 Sep 6, 2026
71ec401
test(bin): pin teardown outcomes for squash-merged rebased branches (…
MortenGad Sep 6, 2026
29015a2
fix(bin): keep supervision armed for registered custom checks (#3860)
gyute Sep 6, 2026
64d3905
fix(bin): resolve Treehouse locks for remote secondmate homes (#3883)
kunchenguid Sep 7, 2026
cf7e2fa
fix(bearings): repair board listening and decision reconciliation (#3…
kunchenguid Sep 7, 2026
1533f47
fix(bin): allow pooled spawns without a git origin (#3885)
kunchenguid Sep 7, 2026
5592cb6
feat(tests): run live harness guards by default when available (#3889)
kunchenguid Sep 7, 2026
6d396da
fix(bin): refuse test runs in the primary checkout when a task marker…
3264studios Sep 7, 2026
d4eb228
fix(bin): bound stale alarms for backlog captain holds (#3842)
mremond Sep 7, 2026
0b9f518
feat(pi): resolve extension-registered providers in the supervision b…
0x7067 Sep 7, 2026
ffd2c89
fix(bin): gate secondmate wake-loop stall alerts on real queue no-pro…
cisrd Sep 7, 2026
36fd955
test(calm): harden the export-DOM render step and record Pi 0.85.1 ev…
cisrd Sep 7, 2026
3af74fe
fix(bin): make every counted wake queue row presentable or retired (#…
cisrd Sep 7, 2026
98b37d4
fix(bin): use system stat for Darwin BSD formats (#3305)
0x7067 Sep 7, 2026
72bfdd0
fix(spawn): carry attribution-off policy in every claude launch (#3945)
NewAiCoder-bot Sep 7, 2026
891dc51
fix(bin): derive watcher beacon staleness grace from poll cadence (#3…
NewAiCoder-bot Sep 8, 2026
b84e0e3
fix(procevent): reap orphaned runners and prevent launch storms (#3904)
kunchenguid Sep 8, 2026
eb8a678
chore: sync fork with upstream main (round 7)
prandelicious Sep 8, 2026
ee41625
no-mistakes(review): Remove duplicated merge-conflict residue
prandelicious Sep 10, 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
4 changes: 3 additions & 1 deletion .agents/skills/process-event-sources/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,15 @@ Supported by tests:
- the handled acknowledgement is generation-keyed to the exact source and sequence, private, path-safe, durable, and idempotent, and is the only thing that stops re-announcement;
- one identity-matched owner per canonical source, across homes that share one underlying source store;
- registration and ownership transitions share one per-source boundary, release is generation-bound, and uncertain process identity preserves the source for retry;
- ownership moves only when the owner is stale and an independent process-group check proves the whole generation gone, so neither a crashed leader nor a reused pid relaxes cleanup while the old group survives; a safely identified surviving group is stopped before replacement, and the claim is kept for retry when it cannot be;
- leaderless PID/PGID-reuse ambiguity preserves the claim without signalling or replacement, as owned by the operating contract in [`docs/configuration.md`](../../../docs/configuration.md#process-to-event-sources-stateprocevent);
- runner lifetime, owner-lease, and launch-pacing guarantees follow the operating contract in [`docs/configuration.md`](../../../docs/configuration.md#process-to-event-sources-stateprocevent);
- stored argv is executed directly, so an argument containing spaces or shell metacharacters is never re-split or interpreted;
- oversized output is bounded rather than published whole or silently dropped.

The `when` adapter's guarantees are part of the operating contract in [`docs/configuration.md`](../../../docs/configuration.md#process-to-event-sources-stateprocevent).

**Not true, and never to be claimed:** at-least-once, no-loss, or lossless delivery, and no generic exactly-once effect either - the handled acknowledgement only stops re-announcement, it says nothing about whether a paired external effect performed before the acknowledgement call actually completed, so a crash between that effect and the call can still repeat the effect on the next replay.
Also never claim that a source cannot refresh its owning home's lease: that rule is confused-agent-grade and a deliberately marker-stripping source is out of scope, per the operating contract in [`docs/configuration.md`](../../../docs/configuration.md#process-to-event-sources-stateprocevent).

The currently published `lavish-axi poll` destructively clears feedback before returning it.
A result lost after that clearing and before the runner reads the process output is unrecoverable, and no firstmate wrapper can close that source-side window.
Expand Down
57 changes: 55 additions & 2 deletions .pi/extensions/fm-branch-supervision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import {
getAgentDir,
keyHint,
ModelRuntime,
type ModelRegistry,
SessionManager,
ToolExecutionComponent,
type AgentSession,
Expand Down Expand Up @@ -644,6 +645,11 @@ export default function (pi: ExtensionAPI) {
// extension plus its model_select event, because createBranch runs at wake
// time with no context of its own. It is what "follow main" applies.
let mainModel: { provider: string; id: string } | null = null;
// Main's own model registry, captured from the contexts Pi hands this
// extension the same way mainModel is. It is the ONLY read path to
// providers an extension registered at runtime (pi-devin-auth's "devin"),
// which the branch's isolated ModelRuntime cannot see on its own.
let mainModelRegistry: ModelRegistry | null = null;

// Main's own current effort needs no such tracking: Pi answers it directly
// on demand, including at wake time. It throws only when the extension
Expand All @@ -657,8 +663,9 @@ export default function (pi: ExtensionAPI) {
}
}

function rememberMainModel(ctx?: { model?: { provider: string; id: string } }): void {
function rememberMainModel(ctx?: { model?: { provider: string; id: string }; modelRegistry?: ModelRegistry }): void {
if (ctx?.model) mainModel = { provider: ctx.model.provider, id: ctx.model.id };
if (ctx?.modelRegistry) mainModelRegistry = ctx.modelRegistry;
}

function deliverBranchHealthNote(text: string): void {
Expand Down Expand Up @@ -708,10 +715,55 @@ export default function (pi: ExtensionAPI) {
// and same user as main, so stored credentials keep their own semantics
// (OAuth stays OAuth, an API key stays an API key) and nothing is ever
// installed, converted, derived, or overwritten here.
// A provider that exists only because an extension registered it into
// main's runtime (pi-devin-auth's "devin", whose streamSimple is the custom
// gRPC path no static catalog can express) is invisible to an isolated
// branch runtime until its registration is copied across. The config object
// carries that streamSimple and oauth wiring by reference, so copying it
// reuses the provider's own registration rather than reimplementing its
// wire protocol; the copy is never persisted and stays scoped to this one
// runtime. One registration that fails to compose must not blind the rest,
// so each copy is isolated. A just-registered provider's auth check has not
// run yet, so the copied providers are refreshed here and every caller's
// hasConfiguredAuth verdict is real rather than the provisional entry
// registration leaves behind.
async function copyExtensionProviders(modelRuntime: ModelRuntime): Promise<void> {
if (!mainModelRegistry) return;
let providerIds: readonly string[];
try {
providerIds = mainModelRegistry.getRegisteredProviderIds();
} catch {
return;
}
const copied: string[] = [];
for (const providerId of providerIds) {
try {
const config = mainModelRegistry.getRegisteredProviderConfig(providerId);
if (config) {
modelRuntime.registerProvider(providerId, config);
copied.push(providerId);
}
} catch {
// A registration that fails to compose in the isolated runtime leaves
// that provider unavailable, exactly as if it were never copied.
}
}
if (copied.length === 0) return;
try {
await modelRuntime.refresh({ providers: copied, allowNetwork: false });
} catch {
// A failed availability refresh is answered by hasConfiguredAuth.
}
}

async function resolveBranchModel(provider: string, modelId: string): Promise<BranchModelResolution> {
const label = `${provider}/${modelId}`;
const modelRuntime = await ModelRuntime.create();
const model = modelRuntime.getModel(provider, modelId) as BranchModel | undefined;
let model = modelRuntime.getModel(provider, modelId) as BranchModel | undefined;
if (!model) {
await copyExtensionProviders(modelRuntime);
model = modelRuntime.getModel(provider, modelId) as BranchModel | undefined;
}
if (!model) return { ok: false, reason: `${label} is unavailable to the isolated branch runtime` };
if (!modelRuntime.hasConfiguredAuth(provider)) {
return { ok: false, reason: `${label} has no configured credentials in the isolated branch runtime` };
Expand Down Expand Up @@ -1659,6 +1711,7 @@ ${context.command}
let available: string[];
try {
const modelRuntime = await ModelRuntime.create();
await copyExtensionProviders(modelRuntime);
available = ctx.modelRegistry
.getAvailable()
.filter((model) => modelRuntime.getModel(model.provider, model.id) && modelRuntime.hasConfiguredAuth(model.provider))
Expand Down
4 changes: 2 additions & 2 deletions bin/backends/herdr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -661,15 +661,15 @@ fm_backend_herdr_presentation_lock_namespace() {

fm_backend_herdr_presentation_lock_namespace_mode() {
if [ "$(uname -s 2>/dev/null)" = Darwin ]; then
stat -f '%Lp' "$1" 2>/dev/null
/usr/bin/stat -f '%Lp' "$1" 2>/dev/null
else
stat -c '%a' "$1" 2>/dev/null
fi
}

fm_backend_herdr_presentation_lock_namespace_uid() {
if [ "$(uname -s 2>/dev/null)" = Darwin ]; then
stat -f '%u' "$1" 2>/dev/null
/usr/bin/stat -f '%u' "$1" 2>/dev/null
else
stat -c '%u' "$1" 2>/dev/null
fi
Expand Down
2 changes: 1 addition & 1 deletion bin/fm-backlog-receive.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ list_keys() { # <file>
lock_age() {
local modified now
if [ "$(uname 2>/dev/null)" = Darwin ]; then
modified=$(stat -f '%m' "$1" 2>/dev/null) || return 1
modified=$(/usr/bin/stat -f '%m' "$1" 2>/dev/null) || return 1
else
modified=$(stat -c '%Y' "$1" 2>/dev/null) || return 1
fi
Expand Down
4 changes: 2 additions & 2 deletions bin/fm-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -938,14 +938,14 @@ x_mode_write_if_changed() {
[ "$parent" != "$dest" ] || return 1
[ -d "$parent" ] && [ ! -L "$parent" ] || return 1
if [ "$(uname)" = Darwin ]; then
parent_device=$(stat -f %d "$parent" 2>/dev/null) || return 1
parent_device=$(/usr/bin/stat -f %d "$parent" 2>/dev/null) || return 1
else
parent_device=$(stat -c %d "$parent" 2>/dev/null) || return 1
fi
if [ -e "$dest" ] || [ -L "$dest" ]; then
fmx_single_link_file_valid "$dest" "$parent_device" || return 1
if [ "$(uname)" = Darwin ]; then
current_mode=$(stat -f %Lp "$dest" 2>/dev/null) || return 1
current_mode=$(/usr/bin/stat -f %Lp "$dest" 2>/dev/null) || return 1
else
current_mode=$(stat -c %a "$dest" 2>/dev/null) || return 1
fi
Expand Down
2 changes: 1 addition & 1 deletion bin/fm-busy-event.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ LOCK="$REC.lock"
# gets a non-numeric token. Detect the platform once and pick the right form,
# exactly as bin/fm-watch.sh does.
if [ "$(uname)" = Darwin ]; then
lock_mtime() { stat -f %m "$1" 2>/dev/null; }
lock_mtime() { /usr/bin/stat -f %m "$1" 2>/dev/null; }
else
lock_mtime() { stat -c %Y "$1" 2>/dev/null; }
fi
Expand Down
48 changes: 38 additions & 10 deletions bin/fm-captain-hold.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
# fm-captain-hold.sh binding <source-id>
# fm-captain-hold.sh complete <origin-id> (--none | <task-id>...)
# fm-captain-hold.sh verify <origin-id>
# fm-captain-hold.sh open <task-id>
# fm-captain-hold.sh open <task-id> [--identity] [--distinguish-absent]
# fm-captain-hold.sh diverged
# fm-captain-hold.sh reconcile list
# fm-captain-hold.sh reconcile close <task-id> --evidence-file <path>
Expand Down Expand Up @@ -157,12 +157,23 @@
# is (not Done, hold kind captain), 1 means it is not, and 2 means the answer
# could not be established, so a caller that must never close a live call can
# treat "cannot tell" as its own case instead of as a no. With
# `--distinguish-absent`, an absent local task returns 3 instead of 1. It prints
# nothing on these predicate results and mutates nothing. bin/fm-teardown.sh asks it before its automatic
# `--distinguish-absent`, an absent local task returns 3 instead of 1.
# It prints nothing on these predicate results and mutates nothing, unless
# `--identity` asks it to print this call's
# LIFECYCLE identity, which it does on an exit 0 only. That identity - the
# hold-set stamp and the count of recorded answers - is what distinguishes two
# successive calls on one task id: re-holding released work starts a new
# lifecycle without necessarily touching the task's status log, so a consumer
# that bounds repeated work per call cannot use the task id alone.
# bin/fm-teardown.sh asks it before its automatic
# backlog close and, on 0, returns the row to Queued with its deliverable
# recorded instead (bin/fm-backlog-transition-lib.sh owns that transition), so
# holding the very work item a question gates is safe; only `answer` with the
# captain's words or evidence-backed `reconcile close` closes the call.
# bin/fm-watch.sh asks it when an ordinary
# crew task reaches a due stale alarm - its open backlog hold need not appear in
# the task's last status line - and on a 0 bounds repeated alarms from new pane
# hashes for the decision.
#
# `diverged` is the read-only guard over the seam between the two records of
# one captain call. See "record divergence" beside command_diverged below.
Expand Down Expand Up @@ -1753,13 +1764,20 @@ EOF
# A row this home does not carry is 3 when the caller requests the distinction;
# every other read failure is a 2, printed to stderr, because a mechanical
# closer must never read "cannot tell" as permission to close.
command_open() { # <task-id> [--distinguish-absent]
local id=${1:-} data state distinguish_absent=0
[ "$#" -ge 1 ] && [ "$#" -le 2 ] || { usage >&2; exit 2; }
if [ "$#" -eq 2 ]; then
[ "$2" = --distinguish-absent ] || { usage >&2; exit 2; }
distinguish_absent=1
fi
command_open() { # <task-id> [--identity] [--distinguish-absent]
local id='' identity=0 distinguish_absent=0 data state show shown_body
while [ "$#" -gt 0 ]; do
case "$1" in
--identity) identity=1 ;;
--distinguish-absent) distinguish_absent=1 ;;
-*) usage >&2; exit 2 ;;
*)
[ -z "$id" ] || { usage >&2; exit 2; }
id=$1
;;
esac
shift
done
case "$id" in
''|*[!A-Za-z0-9._-]*)
printf 'fm-captain-hold: task id must be a non-empty privacy-safe slug: %s\n' "$id" >&2
Expand All @@ -1772,6 +1790,16 @@ command_open() { # <task-id> [--distinguish-absent]
if fm_backlog_row_probe "$data" "$id"; then
state=${FM_BACKLOG_ROW_STATE%% *}
if [ "$state" != "done" ] && [ "$FM_BACKLOG_ROW_HOLD_KIND" = captain ]; then
if [ "$identity" -eq 1 ]; then
show=$(task_show "$id") || {
printf 'fm-captain-hold: captain call %s is open but its record could not be read\n' "$id" >&2
exit 2
}
shown_body=$(show_field "$show" body)
printf '%s#%s\n' \
"$(body_hold_set_timestamp "$(decode_shown_value "$shown_body")")" \
"$(resolution_record_count "$shown_body")"
fi
return 0
fi
return 1
Expand Down
12 changes: 6 additions & 6 deletions bin/fm-classify-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -652,9 +652,9 @@ _fm_open_decisions_file_ident() { # <file> -> strongest available identity
return
fi
if [ "$(uname -s 2>/dev/null)" = Darwin ]; then
ident=$(LC_ALL=C stat -f '%d:%i' "$f" 2>/dev/null) || return 1
epoch=$(LC_ALL=C stat -f '%B' "$f" 2>/dev/null) || epoch=0
if [ "$epoch" != 0 ]; then birth=$(LC_ALL=C stat -f '%FB' "$f" 2>/dev/null) || birth=''; else birth=''; fi
ident=$(LC_ALL=C /usr/bin/stat -f '%d:%i' "$f" 2>/dev/null) || return 1
epoch=$(LC_ALL=C /usr/bin/stat -f '%B' "$f" 2>/dev/null) || epoch=0
if [ "$epoch" != 0 ]; then birth=$(LC_ALL=C /usr/bin/stat -f '%FB' "$f" 2>/dev/null) || birth=''; else birth=''; fi
else
ident=$(LC_ALL=C stat -c '%d:%i' "$f" 2>/dev/null) || return 1
epoch=$(LC_ALL=C stat -c '%W' "$f" 2>/dev/null) || epoch=0
Expand All @@ -671,7 +671,7 @@ _fm_status_file_size() { # <status-file>
return
fi
if [ "$(uname -s 2>/dev/null)" = Darwin ]; then
LC_ALL=C stat -f '%z' "$f" 2>/dev/null
LC_ALL=C /usr/bin/stat -f '%z' "$f" 2>/dev/null
else
LC_ALL=C stat -c '%s' "$f" 2>/dev/null
fi
Expand All @@ -680,7 +680,7 @@ _fm_status_file_size() { # <status-file>
_fm_status_file_mtime() { # <status-file>
local f=$1
if [ "$(uname -s 2>/dev/null)" = Darwin ]; then
LC_ALL=C stat -f '%m' "$f" 2>/dev/null
LC_ALL=C /usr/bin/stat -f '%m' "$f" 2>/dev/null
else
LC_ALL=C stat -c '%Y' "$f" 2>/dev/null
fi
Expand Down Expand Up @@ -1071,7 +1071,7 @@ status_presentation_marker_parse() {

_status_observed_path_state() {
if [ "$(uname -s 2>/dev/null)" = Darwin ]; then
LC_ALL=C stat -f '%HT:%p' "$1" 2>/dev/null
LC_ALL=C /usr/bin/stat -f '%HT:%p' "$1" 2>/dev/null
else
LC_ALL=C stat -c '%F:%f' "$1" 2>/dev/null
fi
Expand Down
12 changes: 9 additions & 3 deletions bin/fm-claude-stop-autoarm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
CONFIG="${FM_CONFIG_OVERRIDE:-$FM_HOME/config}"
GRACE=${FM_GUARD_GRACE:-300}
OWNER_LOCK="$STATE/.claude-autoarm.lock"
FAILURE_NOTICE="$STATE/.claude-autoarm-failure-notified"
FAILURE_ALARM="$STATE/.claude-autoarm-failure-alarmed"
Expand All @@ -94,6 +93,13 @@ esac
# shellcheck source=bin/fm-hook-host-lib.sh
. "$SCRIPT_DIR/fm-hook-host-lib.sh"

# fm-watch.sh touches the liveness beacon once per cycle, immediately before
# its terminal wait, so a healthy watcher's beacon can legitimately age up to
# FM_POLL seconds between touches (docs/turnend-guard.md "Guard grace and the
# poll cadence"). fm_poll_derived_grace (bin/fm-wake-lib.sh) is the single
# owner of that max(300, poll+60) derivation.
GRACE=${FM_GUARD_GRACE:-$(fm_poll_derived_grace)}

# Consume the Stop payload once. The decisions below are state-based; the
# payload is read so a slow writer can never wedge on a full pipe, and its host
# is inspected before anything else runs.
Expand Down Expand Up @@ -217,9 +223,9 @@ while [ "$attempt" -lt "$AUTOARM_ATTEMPTS" ]; do
attempt=$((attempt + 1))
OUT=$(mktemp "$STATE/.claude-autoarm-output.XXXXXX") || OUT=
if [ -n "$OUT" ]; then
"$SCRIPT_DIR/fm-watch-arm.sh" >"$OUT" 2>&1 || true
FM_GUARD_GRACE="$GRACE" "$SCRIPT_DIR/fm-watch-arm.sh" >"$OUT" 2>&1 || true
else
"$SCRIPT_DIR/fm-watch-arm.sh" >/dev/null 2>&1 || true
FM_GUARD_GRACE="$GRACE" "$SCRIPT_DIR/fm-watch-arm.sh" >/dev/null 2>&1 || true
fi

# AFK may have appeared mid-cycle: the daemon owns triage now, so suppress
Expand Down
6 changes: 3 additions & 3 deletions bin/fm-config-inherit-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,23 +103,23 @@ fm_config_source_present() {

fm_inherit_file_mode() {
if [ "$(uname)" = Darwin ]; then
stat -f %Lp "$1" 2>/dev/null
/usr/bin/stat -f %Lp "$1" 2>/dev/null
else
stat -c %a "$1" 2>/dev/null
fi
}

fm_inherit_file_device() {
if [ "$(uname)" = Darwin ]; then
stat -f %d "$1" 2>/dev/null
/usr/bin/stat -f %d "$1" 2>/dev/null
else
stat -c %d "$1" 2>/dev/null
fi
}

fm_inherit_file_link_count() {
if [ "$(uname)" = Darwin ]; then
stat -f %l "$1" 2>/dev/null
/usr/bin/stat -f %l "$1" 2>/dev/null
else
stat -c %h "$1" 2>/dev/null
fi
Expand Down
6 changes: 3 additions & 3 deletions bin/fm-fleet-snapshot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1086,8 +1086,8 @@ case "$FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME" in ''|*[!0-9]*) FM_SNAPSHOT_SECON
# pollute arithmetic input before failing. Select the platform syntax once.
if [ "$(uname 2>/dev/null || true)" = Darwin ]; then
SNAPSHOT_STAT_STYLE=bsd
file_mtime_epoch() { stat -f '%m' "$1" 2>/dev/null || true; }
file_mode_octal() { stat -f '%Lp' "$1" 2>/dev/null || true; }
file_mtime_epoch() { /usr/bin/stat -f '%m' "$1" 2>/dev/null || true; }
file_mode_octal() { /usr/bin/stat -f '%Lp' "$1" 2>/dev/null || true; }
else
SNAPSHOT_STAT_STYLE=gnu
file_mtime_epoch() { stat -c '%Y' "$1" 2>/dev/null || true; }
Expand Down Expand Up @@ -1440,7 +1440,7 @@ bounded_parent_activities_json() { # <status-file>
stat_style=$6
. "$classify"
if [ "$stat_style" = bsd ]; then
size=$(stat -f "%z" "$f" 2>/dev/null) || exit 3
size=$(/usr/bin/stat -f "%z" "$f" 2>/dev/null) || exit 3
else
size=$(stat -c "%s" "$f" 2>/dev/null) || exit 3
fi
Expand Down
Loading