* fix(goal): suppress reserved SILENT sentinel at /api/goal ingress A wake relay POSTing the exact [SILENT] suppression sentinel to /api/goal lets it reach _start_chat_stream_for_session, which persists it as pending_user_message. If 8701 restarts while the turn is pending, session recovery materializes it as a visible _recovered user turn — the same phantom-recovered-turn exposure #7018 closed for chat ingress. Apply the shared _is_silent_control_message() guard immediately after /api/goal session-ID validation and before session lookup or goal-state mutation, returning the same 200 no-op. Matching stays exact and case-sensitive; ordinary kickoff text is unaffected. Add tests mirroring test_silent_control_suppression.py for the goal path (args and text fields, before-lookup suppression, exact-match semantics). Closes #7019 * fix(sessions): keep evicted subagent parents in the import window The sidebar nests a subagent row under its parent only when the parent row is present in the same payload. The visible-window limit was applied as a flat per-row recency slice, so a frozen orchestrator (which stops writing while its leaves keep streaming) lost the recency race against its own leaves and fell outside the window -- promoting those leaves to top-level sidebar rows. Re-add subagent parents that the oversampled candidate set already projected, after the slice. No extra queries, no change to CLI_VISIBLE_SESSION_LIMIT. webui ancestors are deliberately not imported because that sidebar bucket already owns them. Supersedes #7031. * fix(sessions): document and pin the parent-recovery bound (greptile review) Greptile flagged (P1) that a selected subagent child whose parent ranks below the limit * 8 oversample is still promoted to a top-level row. That is real and measured (the parent drops out at candidate #25 of 24), but it is the bound of the design, not a regression: the walk reuses rows the projection already fetched and never issues an extra query. Resolving arbitrarily old ancestors needs an unbounded per-row lookup on the hot sidebar path -- the approach rejected in #7031 -- so the bound is documented and pinned instead. - Docstring: state that `limit` bounds the recency slice, not the row count, so callers must iterate rather than assume len(rows) <= limit; state that recovery is bounded by the oversampled candidate set. - Inline comment: mark the bound at the exact lines Greptile flagged and point at `candidate_limit` as the knob if the window proves too tight. - Tests: cover the candidate-window exhaustion Greptile said was untested -- parent inside the oversample is recovered, parent beyond it stays unresolved -- plus the over-limit return contract and a parent-cycle guard. No behaviour change; 7 tests pass. * fix(share): add _hadAppearance guard to prevent fabricated appearance choice Summary: The inline appearance bootstrap in static/share.html writes hermes-theme and hermes-skin to localStorage unconditionally on every page load, even when the browser had no prior appearance state. This fabricates an explicit user choice on first access via a shared link, making the server-side SETTINGS_DEFAULTS unreachable for deployments that customise the default theme or skin. Root Cause: share.html:9 — the boot IIFE resolves a theme+skin and calls localStorage.setItem() without guarding on whether the user had previously chosen an appearance. The same bug was fixed in index.html by PR #6808 (commit tomtong2015) but share.html was left unchanged. Change: 1. Added _hadAppearance guard before the two localStorage.setItem() calls: var _hadAppearance = localStorage.getItem('hermes-theme') !== null || localStorage.getItem('hermes-skin') !== null; if (_hadAppearance) { setItem('hermes-theme', t); setItem('hermes-skin', s); } 2. The first-paint DOM mutations (classList.add('dark'), dataset.skin) remain outside the guard — only persistence is protected. 3. Synced the skin allowlist with index.html: added neon-soft and neon-paint (zeus and verdigris were already present). Verification: - test_6808_appearance_bootstrap_no_fabricated_choice.py: 11/11 passed covering fresh-browser (no writes), pre-paint fallback, explicit state normalisation, and legacy migration survival. Closes #7030 * fix(tests): pin rebuild budget in issue2513 custom-provider catalog test test (3.13, 4) failed on this PR with: assert "@custom:alpha-proxy:alpha/remote" in alpha_ids E AssertionError: assert '@custom:alpha-proxy:alpha/remote' in {'alpha/sticky'} WARNING api.config:config.py:8355 live provider-catalog rebuild exceeded 4.0s budget - serving fallback, refreshing catalog out-of-band Pre-existing wall-clock flake, not a regression from this PR: this branch touches only api/agent_sessions.py and tests/test_subagent_parent_in_import_ window.py, and both api/config.py and this test file are byte-identical to origin/master. The same shard passed on 3.11 and 3.12. The test never pinned _LIVE_REBUILD_BUDGET_SECONDS, so it raced the global 4s budget in get_available_models(). On a starved runner the cold rebuild overruns, the degraded fallback catalog is served, and the monkeypatched- urlopen model alpha/remote is dropped - leaving only the config-declared sticky model, exactly as CI observed. Force the synchronous (unbounded) rebuild path, matching the existing precedent in tests/test_issue2540_models_endpoint_error.py:20-24. Verified: with the budget forced to 0.001s the unpatched test reproduces the CI assertion verbatim; patched it passes at 0.001s, 0, and default. * fix(#7013): preserve media deny coverage across platforms * fix(docker): keep repository agents out of runtime context * fix(docker): gate image runtime proof behind integration job * test(models): stabilize custom provider catalog regression * Release batch A: 6 low-risk gate-passed fixes (experimental) Batched contributor fixes, each individually Codex-gated during the overnight certifier cycles and re-verified clean-to-ship as a combined stage (Codex SAFE TO SHIP on the combined diff; full suite green except 8 pre-existing approval tests that fail identically on clean origin/master — CI green on same commit). - #7019 (@webtecnica) suppress reserved [SILENT] sentinel at /api/goal - #7031 (@carlotestor) keep evicted subagent parents in the sidebar import window - #7030 (@webtecnica) share.html first-visit appearance guard + skin-id sync - #6853 (@rodboev) exclude repo-root AGENTS.md from the Docker runtime image - #7013 (@webtecnica) test-only: platform-neutral test portability (playwright import guard + media tests served from allowed roots) - test-only (@carlotestor) pin custom-provider catalog rebuild budget (#7054) Co-authored-by: webtecnica <webtecnica@users.noreply.github.com> Co-authored-by: carlotestor <carlotestor@users.noreply.github.com> Co-authored-by: rodboev <rodboev@users.noreply.github.com> --------- Co-authored-by: webtecnica <webtecnica@gmail.com> Co-authored-by: carlotestor <carlotestor@users.noreply.github.com> Co-authored-by: Rod Boev <rod.boev@gmail.com> Co-authored-by: n <a@n> Co-authored-by: webtecnica <webtecnica@users.noreply.github.com> Co-authored-by: rodboev <rodboev@users.noreply.github.com>
83 lines
7.0 KiB
HTML
83 lines
7.0 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Shared Conversation - Hermes</title>
|
|
<meta name="robots" content="noindex,nofollow,noarchive">
|
|
<meta name="referrer" content="same-origin">
|
|
<script>(function(){try{var themes={light:1,dark:1,system:1},skins={codex:1,terracotta:1,default:1,ares:1,mono:1,graphite:1,github:1,slate:1,poseidon:1,sisyphus:1,charizard:1,sienna:1,catppuccin:1,hepburn:1,nous:1,'geist-contrast':1,neon:1,'neon-soft':1,'neon-paint':1,zeus:1,'verdigris':1},legacy={slate:['dark','slate'],solarized:['dark','poseidon'],monokai:['dark','sisyphus'],nord:['dark','slate'],oled:['dark','default']},t=(localStorage.getItem('hermes-theme')||'dark').toLowerCase(),s=(localStorage.getItem('hermes-skin')||'').toLowerCase(),m=legacy[t],theme=m?m[0]:(themes[t]?t:'dark');var pendingExt=s&&s!=='default'&&!skins[s]&&!legacy[s];var skin=skins[s]?s:(pendingExt?s:(m?m[1]:'default'));var _hadAppearance=localStorage.getItem('hermes-theme')!==null||localStorage.getItem('hermes-skin')!==null;if(_hadAppearance){localStorage.setItem('hermes-theme',theme);localStorage.setItem('hermes-skin',skin);}if(theme==='system')theme=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';if(theme==='dark')document.documentElement.classList.add('dark');if(skin!=='default')document.documentElement.dataset.skin=skin;}catch(e){document.documentElement.classList.add('dark');}})()</script>
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
<style>
|
|
html,body{height:auto;min-height:100%}
|
|
body{overflow-y:auto;overflow-x:hidden;display:block}
|
|
.share-shell{min-height:100vh;background:var(--bg);color:var(--text);}
|
|
.share-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px 22px;border-bottom:1px solid var(--border);background:var(--sidebar);position:sticky;top:0;z-index:10}
|
|
.share-brand{display:flex;align-items:center;gap:10px;min-width:0}
|
|
.share-brand-title{font-size:13px;font-weight:700;color:var(--text);letter-spacing:.01em}
|
|
.share-brand-sub{font-size:11px;color:var(--muted)}
|
|
.share-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
|
.share-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 14px;border-radius:10px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:13px;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,color .15s;text-decoration:none}
|
|
.share-btn:hover{background:var(--accent-bg);border-color:var(--accent-bg-strong);color:var(--accent-text)}
|
|
.share-btn:disabled{opacity:.45;cursor:not-allowed}
|
|
.share-wrap{width:min(920px,calc(100vw - 32px));margin:0 auto;padding:28px 0 72px}
|
|
.share-hero{padding:6px 0 18px}
|
|
.share-kicker{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border-radius:999px;background:var(--surface);border:1px solid var(--border2);font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}
|
|
.share-title{margin:16px 0 10px;font-size:32px;line-height:1.1;letter-spacing:-.02em;color:var(--strong,var(--text))}
|
|
.share-meta{font-size:13px;color:var(--muted);line-height:1.6}
|
|
.share-transcript{margin-top:22px;padding:18px 18px 8px;border:1px solid var(--border);border-radius:18px;background:linear-gradient(180deg,color-mix(in srgb,var(--surface) 88%,transparent),color-mix(in srgb,var(--sidebar) 94%,transparent))}
|
|
.share-empty,.share-error{padding:40px 16px;text-align:center;color:var(--muted)}
|
|
.share-error strong{color:var(--text)}
|
|
.share-message{padding:14px 0 18px;border-top:1px solid color-mix(in srgb,var(--border) 78%,transparent)}
|
|
.share-message:first-child{border-top:none;padding-top:4px}
|
|
.share-role{display:flex;align-items:center;gap:10px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:10px}
|
|
.share-role-badge{display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 8px;border-radius:999px;border:1px solid var(--border2);background:var(--surface-subtle);color:var(--text)}
|
|
.share-message[data-role="user"] .share-role-badge{background:var(--accent-bg);border-color:var(--accent-bg-strong);color:var(--accent-text)}
|
|
.share-message-body{padding-left:0;max-width:none}
|
|
.share-foot{margin-top:18px;padding-top:18px;border-top:1px solid var(--border);font-size:12px;color:var(--muted);line-height:1.7}
|
|
@media (max-width: 720px){
|
|
.share-topbar{padding:14px 14px 12px;align-items:flex-start;flex-direction:column}
|
|
.share-wrap{width:min(100vw - 20px,920px)}
|
|
.share-title{font-size:26px}
|
|
.share-transcript{padding:14px 14px 6px;border-radius:16px}
|
|
.share-actions{width:100%}
|
|
.share-btn{flex:1 1 180px}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="share-shell">
|
|
<header class="share-topbar">
|
|
<div class="share-brand">
|
|
<span class="app-titlebar-icon" aria-hidden="true">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="18" height="18" aria-hidden="true"><defs><linearGradient id="share-mark" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#08EBF1"/><stop offset="1" stop-color="#3889FD"/></linearGradient><radialGradient id="share-tile" cx="50%" cy="42%" r="75%"><stop offset="0%" stop-color="#0D3460"/><stop offset="100%" stop-color="#021128"/></radialGradient></defs><rect width="64" height="64" rx="14.3" fill="url(#share-tile)"/><g transform="translate(-8.632 -11.731) scale(0.06483)"><path fill="url(#share-mark)" fill-rule="evenodd" d="M630.5 961.9 C634.9 960.7 638.5 957.9 640.5 953.9 C642.5 950.1 643.3 865.1 641.4 864.3 C640 863.8 623.9 872.5 618.2 876.8 C616.4 878.2 613.8 881.2 612.5 883.5 L610 887.7 610 918.4 C610 951.8 610.2 953.1 615.7 958.3 C618.3 960.8 622.4 962.7 625.5 962.9 C626 963 628.3 962.5 630.5 961.9 Z"/></g></svg>
|
|
</span>
|
|
<div>
|
|
<div class="share-brand-title">Hermes Shared Conversation</div>
|
|
<div class="share-brand-sub">Read-only snapshot</div>
|
|
</div>
|
|
</div>
|
|
<div class="share-actions">
|
|
<button class="share-btn" id="shareCopyBtn" type="button">Copy link</button>
|
|
<a class="share-btn" href="/" id="shareHomeLink">Open Hermes</a>
|
|
</div>
|
|
</header>
|
|
<main class="share-wrap">
|
|
<section class="share-hero">
|
|
<div class="share-kicker">Public share</div>
|
|
<h1 class="share-title" id="shareTitle">Loading...</h1>
|
|
<div class="share-meta" id="shareMeta">Fetching shared conversation...</div>
|
|
</section>
|
|
<section class="share-transcript" id="shareTranscript">
|
|
<div class="share-empty">Loading shared conversation...</div>
|
|
</section>
|
|
<div class="share-foot">
|
|
This link shows a read-only snapshot prepared by Hermes WebUI. It does not expose the original workspace browser, profile settings, or live session controls.
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<script src="/static/ui.js"></script>
|
|
<script src="/static/share.js"></script>
|
|
</body>
|
|
</html>
|