Skip to content

Commit e68f0be

Browse files
committed
feat(0.33.0): live /auto engine board in dashboard + AFFiNE profile + recall/retain/browser directives + kitty bottom tabs
1 parent b2ed695 commit e68f0be

11 files changed

Lines changed: 157 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@ versioning theo [SemVer](https://semver.org). **8sync rule:** mỗi PR cập nh
55

66
## [Unreleased]
77

8+
## [0.33.0] — 2026-06-29
9+
10+
### Added — dashboard surfaces the live `/auto` engine run (real, not demo)
11+
- New `/api/engine` reads the **real** gsd-pi state machine the engine drives at
12+
`<root>/.cache/8sync/engine/state.json`; the Engines page renders a live board — goal · progress
13+
bar · slice/task tree with ✓/▸/○/✗ status + retries · current task (4 s refresh, read-only mirror of
14+
the terminal board). Closes the gap where the dashboard showed the workflow *editor* + engine
15+
*binaries* but never the actual `/auto` run. `{active:false}` when none. Browser-verified, 0 console
16+
errors. (`crates/cli/src/verbs/harness/web.rs`, `web/src/{api.ts,App.tsx}`)
17+
18+
### Added — AFFiNE in the `alexdev` profile
19+
- `affine-bin` — official prebuilt of the open-source Community Edition (AGPL/custom: free, self-hostable,
20+
no cloud lock-in). The from-source `affine` AUR pkg fails upstream (electron-packager zip step), so the
21+
prebuilt is used. (`assets/profiles/alexdev.toml`)
22+
23+
### Changed — always-on directives also prime recall/retain + browser
24+
- `APPEND_SYSTEM.md` (every system prompt, never compacted) + the recall hook now explicitly prime
25+
**`recall`/`reflect` before · `retain` durable facts after** (Mnemopi) and **`browser` to verify any
26+
web/UI change for real** — on top of RULE #0 (code-intel MCPs) + skill ref-paths. Stays terse by design
27+
(the system prompt isn't headroom-compressed; headroom is for tool OUTPUTS).
28+
(`assets/configs/omp/APPEND_SYSTEM.md`, `assets/hooks/8sync-recall.ts`)
29+
- **kitty tab bar moved to the bottom** (`tab_bar_edge bottom`) — easier tab switching. (`setup.rs` renderer)
30+
31+
### Fixed — `8sync harness up` now redeploys the recall hook
32+
- `harness up` refreshed APPEND_SYSTEM/engine/workflow but not the recall hook (only init/bare-harness
33+
did), so hook changes never reached existing machines via `up`. Now it does.
34+
(`crates/cli/src/verbs/harness/up.rs`)
35+
836
## [0.32.1] — 2026-06-29
937

1038
### Fixed — `8sync harness` auto-installs the token-optimization MCPs (no startup error)

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ resolver = "2"
33
members = ["crates/cli"]
44

55
[workspace.package]
6-
version = "0.32.1"
6+
version = "0.33.0"
77
edition = "2021"
88
license = "MIT"
99
authors = ["8sync-dev <8sync.dev.1111@gmail.com>"]

assets/configs/omp/APPEND_SYSTEM.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Reaching for grep/find/Read to EXPLORE first is a violation. Read a raw file onl
1717
- **8sync-cli**`~/.omp/skills/8sync-cli/SKILL.md` — prefer `8sync` verbs over raw shell.
1818
Specialist (open the body only when the task matches): **impeccable** (UI/design — mandatory for any frontend), **assp** (copy/brand), **taste** (anti-slop), **image-routing** (image/PDF/diff).
1919

20-
## Memory + compaction
21-
- `agents/STATE.md` is the live plan — read it first; rewrite it at every phase boundary.
22-
- Record learnings in `agents/KNOWLEDGE.md` (`validated:` / `failure:`); after any change update `CHANGELOG.md`.
23-
- Context auto-compacts at 50% (tune: `8sync harness compaction <pct>`); write a handoff into STATE before it fires.
20+
## Memory, recall & verification
21+
- **`recall` / `reflect` BEFORE** answering anything about past sessions, decisions, or user prefs; **`retain`** durable facts (decisions, conventions, prefs) AFTER. omp Mnemopi long-term memory — the recall hook also auto-injects the live skill index + STATE every turn.
22+
- **`browser`** to verify ANY web / UI / visual change for real (open the page + screenshot/observe) — never claim it works unseen.
23+
- `agents/STATE.md` is the live plan — read it first; rewrite at every phase boundary. Record learnings in `agents/KNOWLEDGE.md` (`validated:` / `failure:`); update `CHANGELOG.md` after any change.
24+
- Context auto-compacts at 50% (`8sync harness compaction <pct>`) — write a handoff into STATE before it fires. This block is never compressed, so it stays terse by design; `headroom_compress` is for large tool OUTPUTS.

assets/hooks/8sync-recall.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default function (pi: HookAPI): void {
4343
function bundle(): string {
4444
const lines: string[] = [
4545
"# 8sync recall — obey ~/.omp/agent/APPEND_SYSTEM.md",
46-
"Code-intel first (codegraph · codebase-memory-mcp · serena · headroom) BEFORE grep/Read; open a skill's SKILL.md before acting.",
46+
"Code-intel first (codegraph · codebase-memory-mcp · serena · headroom) BEFORE grep/Read; recall before / retain durable facts after; browser to verify web/UI; open a skill's SKILL.md before acting.",
4747
];
4848
const skills = skillIndex();
4949
if (skills.length) {

assets/profiles/alexdev.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "alexdev"
2-
description = "alexdev bundle — kitty terminal + fan/LED control (CoolerControl/OpenRGB/Lian Li) + Cloudflare WARP + Vietnamese Unikey"
2+
description = "alexdev bundle — kitty terminal + fan/LED control (CoolerControl/OpenRGB/Lian Li) + Cloudflare WARP + Vietnamese Unikey + AFFiNE (OSS knowledge base)"
33
visibility = "personal"
44

55
extends = [
@@ -11,7 +11,10 @@ extends = [
1111

1212
[packages]
1313
pacman = ["kitty"]
14-
aur = []
14+
# AFFiNE — official prebuilt of the open-source Community Edition (AGPL/custom:
15+
# free, self-hostable, no cloud lock-in). The from-source `affine` AUR pkg fails
16+
# to build upstream (electron-packager zip step), so use the official prebuilt.
17+
aur = ["affine-bin"]
1518

1619
[services]
1720
system_enable = []

crates/cli/src/verbs/harness/up.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ fn refresh_once(env: &env_detect::Env, pull: bool, commit: bool) -> Result<()> {
5353
inject_agents_md(&env.home, &root)?;
5454
inject_subfolder_indexes(&root)?;
5555
let _ = crate::verbs::skill::deploy::ensure_append_system(&env.home);
56+
let _ = crate::verbs::skill::deploy::ensure_recall_hook(&env.home);
5657
let _ = crate::verbs::skill::deploy::ensure_serena_mcp(env);
5758
let _ = crate::verbs::skill::deploy::ensure_engine(&env.home, Some(&root));
5859
let _ = crate::verbs::skill::deploy::cleanup_legacy_gs(&env.home, Some(&root));

crates/cli/src/verbs/harness/web.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ fn api_routes() -> Router<Arc<Ctx>> {
7575
.route("/api/skills/add", post(api_skill_add))
7676
.route("/api/skills/update", post(api_skill_update))
7777
.route("/api/engines", get(api_engines))
78+
.route("/api/engine", get(api_engine))
7879
.route("/api/bench", get(api_bench))
7980
.route("/api/eval", get(api_eval))
8081
.route("/api/memory/:file", get(api_memory_get).post(api_memory_set))
@@ -240,6 +241,59 @@ async fn api_engines(State(ctx): State<Arc<Ctx>>) -> Json<serde_json::Value> {
240241
}))
241242
}
242243

244+
/// Live `/auto` engine run — the REAL gsd-pi state machine the engine drives at
245+
/// `<root>/.cache/8sync/engine/state.json` (NOT demo data). Read-only mirror of
246+
/// the terminal board: goal · progress · slice/task tree · current task. Returns
247+
/// `{active:false}` when no run exists. The engine (driven by `/auto` in omp) is
248+
/// the source of truth; the dashboard displays it, never bypasses its verify gate.
249+
async fn api_engine(State(_ctx): State<Arc<Ctx>>) -> Json<serde_json::Value> {
250+
let root = match detect_current_project_root() {
251+
Some(r) => r,
252+
None => return Json(serde_json::json!({ "active": false })),
253+
};
254+
let raw = match std::fs::read_to_string(root.join(".cache/8sync/engine/state.json")) {
255+
Ok(s) => s,
256+
Err(_) => return Json(serde_json::json!({ "active": false })),
257+
};
258+
let state: serde_json::Value = match serde_json::from_str(&raw) {
259+
Ok(v) => v,
260+
Err(_) => return Json(serde_json::json!({ "active": false })),
261+
};
262+
// Mirror 8sync-engine.ts counts()/findNext() over slices[].tasks[].
263+
let (mut total, mut done, mut blocked) = (0u64, 0u64, 0u64);
264+
let mut current = serde_json::Value::Null;
265+
if let Some(slices) = state.get("slices").and_then(|v| v.as_array()) {
266+
for s in slices {
267+
let Some(tasks) = s.get("tasks").and_then(|v| v.as_array()) else { continue };
268+
for t in tasks {
269+
total += 1;
270+
match t.get("status").and_then(|v| v.as_str()) {
271+
Some("done") => done += 1,
272+
Some("blocked") => blocked += 1,
273+
Some("pending") | Some("in_progress") if current.is_null() => {
274+
current = serde_json::json!({
275+
"slice": s.get("title").cloned().unwrap_or(serde_json::Value::Null),
276+
"task": t.get("title").cloned().unwrap_or(serde_json::Value::Null),
277+
"status": t.get("status").cloned().unwrap_or(serde_json::Value::Null),
278+
});
279+
}
280+
_ => {}
281+
}
282+
}
283+
}
284+
}
285+
Json(serde_json::json!({
286+
"active": true,
287+
"goal": state.get("goal").cloned().unwrap_or(serde_json::Value::Null),
288+
"updatedAt": state.get("updatedAt").cloned().unwrap_or(serde_json::Value::Null),
289+
"total": total,
290+
"done": done,
291+
"blocked": blocked,
292+
"current": current,
293+
"slices": state.get("slices").cloned().unwrap_or(serde_json::json!([])),
294+
}))
295+
}
296+
243297
async fn api_bench(State(ctx): State<Arc<Ctx>>) -> Result<Json<super::bench::BenchMetrics>, ApiErr> {
244298
super::bench::bench_metrics(&ctx.home)
245299
.map(Json)

crates/cli/src/verbs/setup.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ fn render_kitty_conf(wallpaper: Option<&std::path::Path>) -> String {
633633
hide_window_decorations yes
634634
confirm_os_window_close 0
635635
# Tabs
636-
tab_bar_edge top
636+
tab_bar_edge bottom
637637
tab_bar_style powerline
638638
tab_powerline_style slanted
639639
# Colors (glass black + violet accent)

web/src/App.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ function EnginesPage() {
568568
];
569569
return (
570570
<Page title="Engines" sub="Token-opt + file-CRUD stack. Absent engines fall back to slow grep/read.">
571+
<EngineRunBoard />
571572
{isLoading || !data ? <Loading rows={4} /> : error ? <ErrorState message={(error as Error).message} /> : (
572573
<div className="grid">
573574
{engines.map((e) => {
@@ -605,6 +606,50 @@ function EnginesPage() {
605606
);
606607
}
607608

609+
// ── Live /auto engine run (real .cache/8sync/engine/state.json, not demo) ──
610+
function EngineRunBoard() {
611+
const { data } = useQuery({ queryKey: ["engine-run"], queryFn: api.engine, refetchInterval: 4000 });
612+
if (!data) return null;
613+
if (!data.active) {
614+
return (
615+
<div className="card" style={{ marginBottom: 16 }}>
616+
<div className="tile-head"><strong>/auto engine</strong><span className="tag warn">idle</span></div>
617+
<p className="tile-hint">No active run. Start one in omp with <code>/auto &lt;goal&gt;</code> — this board mirrors the real <code>.cache/8sync/engine/state.json</code>.</p>
618+
</div>
619+
);
620+
}
621+
const total = data.total ?? 0, done = data.done ?? 0, blocked = data.blocked ?? 0;
622+
const pct = total > 0 ? Math.round((done * 100) / total) : 0;
623+
const icon = (s: string) => (s === "done" ? "✓" : s === "in_progress" ? "▸" : s === "blocked" ? "✗" : "○");
624+
const cls = (s: string) => (s === "done" ? "ok" : s === "blocked" ? "warn" : "");
625+
return (
626+
<div className="card" style={{ marginBottom: 16 }}>
627+
<div className="tile-head">
628+
<strong>/auto engine — live run</strong>
629+
<span className="tag ok">{done}/{total} done{blocked ? ` · ${blocked} blocked` : ""}</span>
630+
</div>
631+
{data.goal && <p className="tile-hint" style={{ marginTop: 0 }}>{data.goal}</p>}
632+
<div style={{ height: 6, borderRadius: 3, background: "rgba(255,255,255,.08)", overflow: "hidden", margin: "8px 0" }}>
633+
<div style={{ width: `${pct}%`, height: "100%", background: "#7c5cff" }} />
634+
</div>
635+
{data.current && (
636+
<p className="tile-sub">▸ current: <strong>{data.current.task}</strong> <span className="tag">{data.current.slice}</span></p>
637+
)}
638+
{(data.slices ?? []).map((s) => (
639+
<div key={s.id ?? s.title} style={{ marginTop: 8 }}>
640+
<div style={{ fontWeight: 600, fontSize: 13, margin: "6px 0 4px" }}>{s.title}</div>
641+
{s.tasks.map((t) => (
642+
<div key={t.id ?? t.title} style={{ display: "flex", gap: 8, alignItems: "center", padding: "2px 0", fontSize: 13 }}>
643+
<span className={`tag ${cls(t.status)}`}>{icon(t.status)}</span>
644+
<span>{t.title}{t.retries ? ` · ${t.retries} retries` : ""}</span>
645+
</div>
646+
))}
647+
</div>
648+
))}
649+
</div>
650+
);
651+
}
652+
608653
// ── Bench ──────────────────────────────────────────────────────────────────
609654
function BenchPage() {
610655
const { data, error, refetch, isFetching } = useQuery({ queryKey: ["bench"], queryFn: api.bench, enabled: false });

0 commit comments

Comments
 (0)