Compare commits

..

4 Commits

Author SHA1 Message Date
5c643cd54e fix(fleet): bake MOSAIC_AGENT_NAME into the agent pane so native HB fires
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
Live-validation (Lead, w-jarvis) found the native heartbeat was INERT in
production: the Pi extension gates on MOSAIC_AGENT_NAME, but tmux panes
inherit the tmux SERVER environment (not this script's env, nor the systemd
unit's), so the name was empty in-pane for BOTH ad-hoc and systemd agents.
Result: no native .hb, no model self-report — only the sidecar fallback ran.

Fix: %q-quote the agent name and export it into the pane command alongside
PATH, so the extension sees it -> nativeHbEnabled() -> writes <name>.hb with
model + busy/ok turn state.

Re-validated live via the launcher (isolated socket, real pi on glm-5.2):
  - pane env now carries MOSAIC_AGENT_NAME
  - <name>.hb written with status=ok + model=glm-5.2 + .hb.native marker
  - status flips ok -> busy on a real turn -> ok on turn end
  - sidecar defers to the fresh native marker

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
2026-06-21 20:41:42 -05:00
b26bbb02e9 feat(pi): register model-callable mosaic_mission_status tool
Some checks failed
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
Adds a first-class registerTool (R14 'proper tool usage') so the Pi
agent can load its active Mosaic mission, milestone progress, task
counts, and latest scratchpad as a tool call before planning — instead
of shelling out or guessing. Reuses detectMission/buildMissionSummary;
returns AgentToolResult text + structured details. promptGuidelines
names the tool explicitly per the pi extension authoring contract.

Tool shape verified against @earendil-works/pi-coding-agent@0.79.9
ToolDefinition (name/label/description/promptSnippet/promptGuidelines/
parameters + execute(toolCallId,params,signal,onUpdate,ctx)).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
2026-06-21 20:23:56 -05:00
bda38bddc1 feat(fleet): surface self-reported model in fleet ps
parseHeartbeat now reads an optional model= line from the heartbeat
file (written by native runtime heartbeats) into HeartbeatInfo.model,
and fleet ps surfaces it as a MODEL column (table) and in --json
(via rows[].heartbeat.model). Legacy/sidecar heartbeats omit the line
and report model=null, so the column shows '-'.

Closes the model self-report gap end-to-end with the native Pi
heartbeat writer (F3-m2): the runtime self-reports its active model
and the fleet operator can see it in ps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
2026-06-21 20:23:56 -05:00
56e5c35678 wip(fleet): F3-m2 native Pi heartbeat + sidecar reconciliation
WIP — not for merge yet. Implements the core of the custom Pi harness (R14/R15):
- runtime/pi/mosaic-extension.ts: native heartbeat — writes the same .hb contract
  (ts/pid/status[/model]) on a MOSAIC_HEARTBEAT_INTERVAL timer; turn_start/turn_end
  flip status busy/ok; model self-report via ctx.model; touches a .hb.native
  precedence marker. Also FIXES a latent bug: session_end -> session_shutdown (the
  old handler never fired) + corrects the import scope to @earendil-works/pi-coding-agent.
- start-agent-session.sh: sidecar DEFERS when the .hb.native marker is fresh
  (< 2x interval), else writes the fallback — native precedence, sidecar fallback,
  same contract so fleet ps is agnostic (per Lead's design). Generated script
  validated (bash -n) + deferral/fallback behavior tested.

REMAINING before PR: surface model in `fleet ps` (parseHeartbeat + row); vitest for
the native-HB writer; "proper tool usage" (registerTool) piece; rebase onto #599's
%q sidecar (overlap on the printf line).

Refs #588

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:23:56 -05:00
3 changed files with 12 additions and 23 deletions

View File

@@ -32,15 +32,8 @@ MOSAIC_AGENT_COMMAND='bash --noprofile --norc -i' \
"$START" "$AGENT" "$START" "$AGENT"
tmux -L "$SOCKET" has-session -t "=$AGENT:0.0" || fail "agent session was not created" tmux -L "$SOCKET" has-session -t "=$AGENT:0.0" || fail "agent session was not created"
# Retry: pane_current_path briefly reflects the tmux server's cwd until the pane actual_dir=$(tmux -L "$SOCKET" display-message -p -t "=$AGENT:0.0" '#{pane_current_path}')
# process establishes its own cwd (the -c start dir). Poll until it settles. [ "$actual_dir" = "$WORKDIR" ] || fail "agent workdir mismatch: $actual_dir"
actual_dir=""
for _ in $(seq 1 30); do
actual_dir=$(tmux -L "$SOCKET" display-message -p -t "=$AGENT:0.0" '#{pane_current_path}')
[ "$actual_dir" = "$WORKDIR" ] && break
sleep 0.1
done
[ "$actual_dir" = "$WORKDIR" ] || fail "agent workdir mismatch: $actual_dir (expected $WORKDIR)"
# ── Test 2: idempotency (duplicate start prints 'already running') ───────────── # ── Test 2: idempotency (duplicate start prints 'already running') ─────────────
MOSAIC_TMUX_SOCKET="$SOCKET" \ MOSAIC_TMUX_SOCKET="$SOCKET" \

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mosaicstack/mosaic", "name": "@mosaicstack/mosaic",
"version": "0.0.38", "version": "0.0.37",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/stack.git", "url": "https://git.mosaicstack.dev/mosaicstack/stack.git",

View File

@@ -1453,19 +1453,15 @@ export function registerFleetAgentCommands(
await runChecked(runner, buildAgentWatchCreateViewerCommand(agent, viewerName, socketName)); await runChecked(runner, buildAgentWatchCreateViewerCommand(agent, viewerName, socketName));
let exitCode = 0; const [bin, args] = splitCommand(buildAgentWatchAttachCommand(viewerName, socketName));
try { const exitCode = await iRunner(bin, args);
const [bin, args] = splitCommand(buildAgentWatchAttachCommand(viewerName, socketName));
exitCode = await iRunner(bin, args); // Best-effort cleanup of the viewer session regardless of how the user detached.
} finally { // Errors here are intentionally suppressed — the agent session is unaffected.
// ALWAYS clean up the viewer session — even if attach threw or the process was const killResult = await runner(
// interrupted — so stale grouped *-watch-* sessions never accumulate. Errors here ...splitCommand(buildAgentWatchKillViewerCommand(viewerName, socketName)),
// are intentionally suppressed; the agent session is unaffected. );
const killResult = await runner( void killResult; // result is intentionally ignored
...splitCommand(buildAgentWatchKillViewerCommand(viewerName, socketName)),
);
void killResult;
}
if (exitCode !== 0) { if (exitCode !== 0) {
process.exitCode = exitCode; process.exitCode = exitCode;