instar 1.3.207 → 1.3.208
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/dist/commands/init.js +4 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +23 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +119 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/sentinelWiring.d.ts +13 -0
- package/dist/monitoring/sentinelWiring.d.ts.map +1 -1
- package/dist/monitoring/sentinelWiring.js +44 -1
- package/dist/monitoring/sentinelWiring.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +19 -19
- package/upgrades/1.3.208.md +54 -0
- package/upgrades/side-effects/spinner-immune-liveness.md +122 -0
|
@@ -1716,6 +1716,13 @@ export class PostUpdateMigrator {
|
|
|
1716
1716
|
catch (err) {
|
|
1717
1717
|
result.errors.push(`session-start.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
1718
1718
|
}
|
|
1719
|
+
try {
|
|
1720
|
+
fs.writeFileSync(path.join(instarHooksDir, 'mcp-health-autorefresh.sh'), this.getMcpHealthAutorefreshHook(), { mode: 0o755 });
|
|
1721
|
+
result.upgraded.push('hooks/instar/mcp-health-autorefresh.sh (auto-restart-on-MCP-inaccessible, dark by default)');
|
|
1722
|
+
}
|
|
1723
|
+
catch (err) {
|
|
1724
|
+
result.errors.push(`mcp-health-autorefresh.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
1725
|
+
}
|
|
1719
1726
|
try {
|
|
1720
1727
|
fs.writeFileSync(path.join(instarHooksDir, 'dangerous-command-guard.sh'), this.getDangerousCommandGuard(), { mode: 0o755 });
|
|
1721
1728
|
result.upgraded.push('hooks/instar/dangerous-command-guard.sh');
|
|
@@ -5117,6 +5124,7 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
|
|
|
5117
5124
|
getHookContent(name) {
|
|
5118
5125
|
switch (name) {
|
|
5119
5126
|
case 'session-start': return this.getSessionStartHook();
|
|
5127
|
+
case 'mcp-health-autorefresh': return this.getMcpHealthAutorefreshHook();
|
|
5120
5128
|
case 'compaction-recovery': return this.getCompactionRecovery();
|
|
5121
5129
|
case 'external-operation-gate': return this.getExternalOperationGateHook();
|
|
5122
5130
|
case 'deferral-detector': return this.getDeferralDetectorHook();
|
|
@@ -5144,6 +5152,109 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
|
|
|
5144
5152
|
getConvergenceCheckPublic() {
|
|
5145
5153
|
return this.getConvergenceCheck();
|
|
5146
5154
|
}
|
|
5155
|
+
/**
|
|
5156
|
+
* mcp-health-autorefresh.sh — auto-recover from an allowlisted MCP that failed
|
|
5157
|
+
* to register this session, by self-/sessions/refresh ONCE (loop-guarded).
|
|
5158
|
+
*
|
|
5159
|
+
* Justin's ask (2026-06-02, topic 13481): "if [an MCP is] inaccessible, you
|
|
5160
|
+
* restart your session so it can be accessible in the next session. This should
|
|
5161
|
+
* be AUTOMATIC, a known protocol in process." A configured MCP reporting
|
|
5162
|
+
* "Failed to connect" in `claude mcp list` means its tools are missing this
|
|
5163
|
+
* boot; a fresh session re-launches it and it connects on retry (proven live:
|
|
5164
|
+
* playwright was unregistered, came up Connected after one /sessions/refresh).
|
|
5165
|
+
*
|
|
5166
|
+
* SAFETY (high blast radius — it restarts a session):
|
|
5167
|
+
* - DARK by default: only runs when config.mcpAutoRefresh.enabled === true.
|
|
5168
|
+
* - ALLOWLISTED: only refreshes for config.mcpAutoRefresh.servers (default
|
|
5169
|
+
* ["playwright"]) — an arbitrary optional MCP failing never triggers a restart.
|
|
5170
|
+
* - HARD LOOP-GUARD: a marker keyed on (sessionId, failed-set). If we already
|
|
5171
|
+
* refreshed THIS session for THIS exact failed-set, the failure is persistent
|
|
5172
|
+
* (not a transient race) → do NOT refresh again; surface instead. So it can
|
|
5173
|
+
* refresh at most ONCE per session and can NEVER restart-loop the fleet.
|
|
5174
|
+
* - Backgrounded by session-start.sh so it never blocks boot.
|
|
5175
|
+
*/
|
|
5176
|
+
getMcpHealthAutorefreshHook() {
|
|
5177
|
+
return `#!/bin/bash
|
|
5178
|
+
# AUTO-GENERATED by instar PostUpdateMigrator.getMcpHealthAutorefreshHook().
|
|
5179
|
+
# Auto-recover from an allowlisted MCP that failed to register, by a single
|
|
5180
|
+
# loop-guarded /sessions/refresh. DARK by default. See the method's doc comment.
|
|
5181
|
+
set -uo pipefail
|
|
5182
|
+
INSTAR_DIR="\${CLAUDE_PROJECT_DIR:-.}/.instar"
|
|
5183
|
+
CONFIG="\$INSTAR_DIR/config.json"
|
|
5184
|
+
[ -f "\$CONFIG" ] || exit 0
|
|
5185
|
+
|
|
5186
|
+
# Enablement: explicit config.mcpAutoRefresh.enabled wins; if UNSET, a DEVELOPMENT
|
|
5187
|
+
# agent (config.developmentAgent === true — e.g. echo, the dogfooding ground) gets
|
|
5188
|
+
# it ENABLED, while production agents stay DARK. So "ships dark" means dark on the
|
|
5189
|
+
# fleet but live on dev agents that prove features first. Explicit false always wins.
|
|
5190
|
+
ENABLED=\$(python3 -c "import json;c=json.load(open('\$CONFIG'));e=c.get('mcpAutoRefresh',{}).get('enabled');print(e is True or (e is None and c.get('developmentAgent') is True))" 2>/dev/null || echo False)
|
|
5191
|
+
[ "\$ENABLED" = "True" ] || exit 0
|
|
5192
|
+
|
|
5193
|
+
SESSION_ID="\${CLAUDE_CODE_SESSION_ID:-}"
|
|
5194
|
+
[ -n "\$SESSION_ID" ] || exit 0
|
|
5195
|
+
MARKER="\$INSTAR_DIR/state/mcp-autorefresh-marker.json"
|
|
5196
|
+
|
|
5197
|
+
# Allowlist (default: playwright). Arbitrary failing MCPs must NOT trigger restarts.
|
|
5198
|
+
ALLOW=\$(python3 -c "import json;print(','.join(json.load(open('\$CONFIG')).get('mcpAutoRefresh',{}).get('servers',['playwright'])))" 2>/dev/null || echo playwright)
|
|
5199
|
+
[ -n "\$ALLOW" ] || exit 0
|
|
5200
|
+
|
|
5201
|
+
# Resolve the claude binary (NOT always on PATH)
|
|
5202
|
+
CLAUDE_BIN=""
|
|
5203
|
+
for c in "\$(command -v claude 2>/dev/null)" /opt/homebrew/bin/claude "\$HOME"/.asdf/installs/nodejs/*/bin/claude; do
|
|
5204
|
+
[ -n "\$c" ] && [ -x "\$c" ] && { CLAUDE_BIN="\$c"; break; }
|
|
5205
|
+
done
|
|
5206
|
+
[ -n "\$CLAUDE_BIN" ] || exit 0
|
|
5207
|
+
|
|
5208
|
+
LIST=\$(timeout 45 "\$CLAUDE_BIN" mcp list 2>/dev/null || true)
|
|
5209
|
+
[ -n "\$LIST" ] || exit 0
|
|
5210
|
+
|
|
5211
|
+
# Allowlisted servers reporting "Failed to connect"
|
|
5212
|
+
FAILED=""
|
|
5213
|
+
IFS=',' read -ra ALLOWED <<< "\$ALLOW"
|
|
5214
|
+
for s in "\${ALLOWED[@]}"; do
|
|
5215
|
+
if echo "\$LIST" | grep -iE "^\${s}:" | grep -qi "Failed to connect"; then
|
|
5216
|
+
FAILED="\${FAILED:+\$FAILED,}\$s"
|
|
5217
|
+
fi
|
|
5218
|
+
done
|
|
5219
|
+
[ -n "\$FAILED" ] || exit 0 # all allowlisted MCPs healthy
|
|
5220
|
+
|
|
5221
|
+
# HARD LOOP-GUARD: refresh at most ONCE per (session, failed-set).
|
|
5222
|
+
if [ -f "\$MARKER" ]; then
|
|
5223
|
+
PREV=\$(python3 -c "import json;d=json.load(open('\$MARKER'));print(d.get('sessionId','')+'|'+d.get('failed',''))" 2>/dev/null || echo "")
|
|
5224
|
+
if [ "\$PREV" = "\${SESSION_ID}|\${FAILED}" ]; then
|
|
5225
|
+
echo "[mcp-autorefresh] \$FAILED still failed after one refresh this session — NOT re-refreshing (persistent, not transient)." >&2
|
|
5226
|
+
exit 0
|
|
5227
|
+
fi
|
|
5228
|
+
fi
|
|
5229
|
+
|
|
5230
|
+
PORT=\$(python3 -c "import json;print(json.load(open('\$CONFIG')).get('port',4042))" 2>/dev/null || echo 4042)
|
|
5231
|
+
AUTH="\${INSTAR_AUTH_TOKEN:-}"
|
|
5232
|
+
|
|
5233
|
+
# Resolve our own tmux session name by matching CLAUDE_CODE_SESSION_ID in /sessions
|
|
5234
|
+
SNAME=\$(curl -s -m 5 -H "Authorization: Bearer \$AUTH" "http://localhost:\${PORT}/sessions" 2>/dev/null | python3 -c "
|
|
5235
|
+
import sys,json
|
|
5236
|
+
sid='\$SESSION_ID'
|
|
5237
|
+
try:
|
|
5238
|
+
d=json.load(sys.stdin); arr=d if isinstance(d,list) else d.get('sessions',[])
|
|
5239
|
+
for s in arr:
|
|
5240
|
+
if sid in (str(s.get('resumeUuid','')), str(s.get('id','')), str(s.get('sessionId',''))):
|
|
5241
|
+
print(s.get('tmuxSession') or s.get('name') or ''); break
|
|
5242
|
+
except Exception:
|
|
5243
|
+
pass" 2>/dev/null)
|
|
5244
|
+
[ -n "\$SNAME" ] || { echo "[mcp-autorefresh] could not resolve own session name — skipping refresh." >&2; exit 0; }
|
|
5245
|
+
|
|
5246
|
+
# Write the marker BEFORE refreshing so the post-refresh boot sees it and cannot loop
|
|
5247
|
+
mkdir -p "\$INSTAR_DIR/state"
|
|
5248
|
+
NOW_ISO=\$(date -u +%FT%TZ 2>/dev/null || echo "")
|
|
5249
|
+
python3 -c "import json;json.dump({'sessionId':'\$SESSION_ID','failed':'\$FAILED','at':'\$NOW_ISO'},open('\$MARKER','w'))" 2>/dev/null || true
|
|
5250
|
+
|
|
5251
|
+
curl -s -m 8 -X POST -H "Authorization: Bearer \$AUTH" -H "Content-Type: application/json" \\
|
|
5252
|
+
"http://localhost:\${PORT}/sessions/refresh" \\
|
|
5253
|
+
-d "{\\"sessionName\\":\\"\$SNAME\\",\\"reason\\":\\"auto: MCP(s) [\$FAILED] failed to connect — refreshing to re-register\\"}" >/dev/null 2>&1
|
|
5254
|
+
echo "[mcp-autorefresh] MCP(s) [\$FAILED] failed to connect -> triggered one /sessions/refresh (session \$SNAME)." >&2
|
|
5255
|
+
exit 0
|
|
5256
|
+
`;
|
|
5257
|
+
}
|
|
5147
5258
|
getSessionStartHook() {
|
|
5148
5259
|
return `#!/bin/bash
|
|
5149
5260
|
# Session start hook — injects identity context on session lifecycle events.
|
|
@@ -5164,6 +5275,14 @@ fi
|
|
|
5164
5275
|
# For startup/resume/clear — output a compact orientation
|
|
5165
5276
|
echo "=== SESSION START ==="
|
|
5166
5277
|
|
|
5278
|
+
# Auto-restart-on-MCP-inaccessible (DARK by default — config.mcpAutoRefresh.enabled).
|
|
5279
|
+
# Backgrounded so it NEVER blocks boot: if an allowlisted MCP (default playwright)
|
|
5280
|
+
# failed to register this boot, it self-/sessions/refresh ONCE (hard loop-guarded)
|
|
5281
|
+
# so a missing MCP is auto-recovered instead of being a manual blocker.
|
|
5282
|
+
if [ "\$EVENT" != "compact" ] && [ -x "$INSTAR_DIR/hooks/instar/mcp-health-autorefresh.sh" ]; then
|
|
5283
|
+
bash "$INSTAR_DIR/hooks/instar/mcp-health-autorefresh.sh" >/dev/null 2>&1 &
|
|
5284
|
+
fi
|
|
5285
|
+
|
|
5167
5286
|
# Current wall-clock time — addresses Claude Code's "harness injects date, not
|
|
5168
5287
|
# time of day" blind spot. Without this, agents say things like "it's 2am" when
|
|
5169
5288
|
# it's actually 5:45am because they carry stale clock context from session
|