Conversation
Author
3 tasks
Owner
|
beta旗舰暂不进行大特性合入,稳定性为主,后续阶段再评估 |
xielixing
force-pushed
the
builtin/loopx-miniapp
branch
2 times, most recently
from
September 15, 2026 07:10
be78844 to
79194cc
Compare
Replaces GCWing#2382, which was closed when main was replaced with the 1.0.0 codebase; this branch is based on the new main and reapplies the built-in LoopX console MiniApp for OpenBitFun. Highlights: - Built-in MiniApp assets (product-domains/src/miniapp/builtin/assets/bitfun-loopx) registered via BUILTIN_APPS with the content-hash seed policy; private app.loopx.* bridge is injected only for the verified built-in identity (builtin-bitfun-loopx). - LoopX contracts (types/ports/policy/bridge) in product-domains; pinned sidecar CLI adapter + GitHub intake + worktree service behind the new services-integrations 'miniapp-loopx' feature. - Core LoopX controller, agent adapter, store, subscriber, tool activity in assembly/core (custom-agent-runner path per the LoopX cooperative runner contract); desktop Tauri surface (7 commands) with Product Operation Registry rows (Unsupported / ControllerLocal / REFUSED). - Bundled loopx sidecar build pipeline: scripts/build-loopx.mjs (pinned v0.5.1 @ 1bb42f4c) wired into desktop-tauri-build.mjs and dev.cjs, Python 3.13 in the packaging CI, THIRD_PARTY_NOTICES obligations. - Web UI: LoopxAPI/loopxBridgeProtocol, bridge + flow-chat wiring, locales. - Dropped the closed-PR installer-lockfile commit: the 1.0.0 repo pins generated installer lockfiles out of git (OpenBitFun-Installer policy). Verified: cargo check -p openbitfun-product-domains --features miniapp; cargo check -p openbitfun-services-integrations with miniapp-runtime,miniapp-loopx,web-tools; cargo check -p openbitfun-core --features product-full; cargo check -p openbitfun-desktop; cargo test -p openbitfun-product-domains remote_surface (19/19); pnpm run capabilities:generate; pnpm --dir src/web-ui run gen:types; pnpm run type-check:web; node --test scripts/desktop-tauri-build.test.mjs.
…fy approval copy - loopx_cli.rs: decode sidecar stdout/stderr/payload as strict UTF-8 first, then GBK (the PyInstaller bundle ignores PYTHONUTF8 and always emits the ANSI code page, so lossy UTF-8 decode turned Chinese gate messages into U+FFFD mojibake); keep PYTHONUTF8/PYTHONIOENCODING for the managed-source Python entrypoint; add encoding_rs behind the miniapp-loopx feature - ui.js/index.html: open the raw request by default; rewrite approve/reject/recommendation copy to name the concrete boundary (writes/commits, builds, installs, real-run validation) and why approval is needed; stop duplicating the request text in the decision card
Bundle of adapter-level fixes driven by a live A/B against the loopx 0.5.1 + codex reference run (same three fixture issues on both hosts): - CLI adapter: inject --runtime-root <worktree>/.loopx/runtime on every loopx invocation; bootstrap passes --state-file into .loopx/goals and (after bootstrap) patches the registry common_runtime_root to the project-local runtime plus re-points legacy state_file away from .codex paths, so the worktree stays fully .loopx-namespaced and never touches the shared ~/.codex/loopx global registry. - Turn instruction: append the pinned CLI reference and the verbatim pinned workflow-skill documents (loopx-project + loopx-self-repair SKILL.md) on the FIRST turn of each agent session only (codex-style session-start skill load; later turns carry a short pointer so prompt cache and per-turn cost stay low). Closing-ceremony note carries the exact required no_followup flag set and vision contract requirements. - shell_analysis: accept powershell/pwsh/cmd dialects with hard input limits so the complete-shell guard stops failing closed on every Windows ExecCommand (whoami included). - MiniApp UI: preserve the technical-receipt <details> open state across re-renders (was collapsing on every snapshot attach); suite-level pause_all/resume_all allowed by the web-ui bridge guard. - build-loopx: bundle loopx skills via --add-data so the frozen sidecar ships the workflow-skill data (verified workflow-skills --install). - AGENTS.md: record the codex-comparison methodology and the root-cause facts (runtime root localization, required closure flags, dialect guard) for future maintainers.
- Seed the pinned LoopX reference (CLI help reference + verbatim official workflow-skill documents) into .loopx/pinned-loopx-reference.md when the workspace connects; the turn instruction now only carries a short pointer (read once per session, reuse from context) instead of pasting ~130KB into every instruction - mirrors how a LoopX codex-style host loads its workflow skills, and keeps per-turn prompts cache-friendly. - Slim LOOPX_CLOSING_CEREMONY_NOTE to host facts only: follow the reference document for schemas/flags; typed refusal -> apply the named parameter once, blocker after two ordered attempts; runtime stays project-local. The previous hand-written flag listing duplicated the docs and the 'minimal evidence' guidance caused repository_context=not_provided drift, so both were removed.
… terminal UX
Three live A/B runs against the loopx 0.5.1 + codex reference (same three
fixture issues) drove this batch; every fix cites its observed failure.
Execution parity (issue 1 went 16.7min -> 9.8min, faster than codex):
- Re-entry instructions now carry copy-ready settlement commands mirroring
the pinned v0.5.1 canonical templates (effect_program.py writeback,
autonomous_replan_obligation.py replan ACK with accountable
delivery-outcome): the outer-controller envelope only ships a bare
template, and agents re-derived flags from help text into 42 typed
refusals ("turn-scoped refresh-state requires an accountable
--delivery-outcome"; surface_only is not accountable).
- The replan ACK guidance embeds the exact goal_vision_replan_contract_v0
packet shape with enforced budgets - the agent's own blocker todo asked
the host to document this schema.
- Agent sessions now continue across turns of one goal (codex exec resume
parity): settlement keeps the session for Queued/WaitingForUser turns,
discards it on terminal/failure, and stale ids fall back to fresh
sessions. Continued turns get an "already loaded" reference pointer
(read policy has a single source: exactly one Read directive on the
first turn, zero afterwards).
- agent-onboard pack is fetched after register-agent so it carries the
registered agent id; monitor-recheck clock skew clamps at zero.
Owner-gated publishes (A9):
- Environment boundary now forbids git push / gh pr create / gh issue
comment / gh pr merge unless the turn contract explicitly carries the
approval, mirroring loopx requires_user_gate_before_external_write; a
todo's text ("open a PR") is a plan description, not an authorization.
Verified: 0 push/PR executions in the follow-up run (previously a PR
was opened without approval).
Truthful projections (A7/A8):
- A user wait without a typed user_gate (e.g. the owner review/merge
queue entry recorded after a PR) now projects as an owner-action
summary and parks the task as waiting instead of failing the whole
inspection and marking finished work recovery_required.
- The replan-lineage salvage accepts every typed envelope the pinned CLI
still prints under that contract error (live settlement previously
failed on an open blocker todo) and projects RunNow/Complete/Wait from
the envelope shape.
Terminal UX:
- Completed goals render a plain-language outcome note (GitHub activity
does not restart a finished goal) instead of the agent's control-plane
next-step wording; verdict badges show the concrete reason (wont-fix
rationale, missing info) instead of a repeated static note; the
rejected-options block is gone; an empty issue body no longer renders
as an endless "loading" placeholder.
- Agents must keep control-plane vocabulary out of human-facing summary
fields, and a terminal-rejected quota spend is not a blocker (the goal
is simply closed); completed tasks no longer render a blockers card.
- GitHub data must go through the gh CLI (direct WebFetch gets 403).
LoopX knowledge stays pinned: all embedded shapes (delivery outcomes,
vision contract, closeout flags) cite the pinned v0.5.1 validation code
by file and are regenerated when LOOPX_PINNED_VERSION moves. See the
bitfun-loopx AGENTS.md for the full run evidence and methodology.
LoopX v1.0.x moved the control-plane core (coordination state, turn envelopes, vision checkpoints) to a managed TypeScript effect runtime the Python sidecar starts on demand via `node --experimental-strip-types`. Verified live against a clean v1.0.1 checkout: bootstrap fails closed with "LoopX Effect runtime requires Node.js 22.6.0 or newer" when Node is absent, so the upgrade ships a Node dependency surface instead of bundling Node (installer size). Runtime compatibility (each verified against the v1.0.1 source and the rebuilt frozen sidecar, full chain bootstrap -> register-agent -> todo -> turn plan -> guard -> refresh-state -> spend -> complete -> history): - Every BitFun argv builder stays compatible; register-agent's new shape (no --project, global-registry write behind --execute) is exactly what BitFun already passes. - All schemas BitFun parses are unchanged (turn_plan/envelope v0, workflow plan packet v0, settlement identity v0+v1, vision contract v0, progress observation v0, command reference v0). - The turn-scoped refresh-state refusal was reworded but stays semantically equal (outcome_progress still qualifies); lineage error text, vision budgets, terminal-no-followup semantics, envelope budget all identical. - Skills content: only loopx-self-repair and loopx-pr-review changed; pinned resources regenerated wholesale from v1.0.1 (CLI help reference recaptured across all eight commands). Changes: - Pin constants -> 1.0.1 / v1.0.1 / 7f2a020b (adapter, contracts, tests). - build-loopx.mjs: stage the control-plane .ts/.json runtime sources as PyInstaller data (subset staging keeps .py modules out of the data area where loose sources could shadow frozen modules); compliance files now follow what the checkout carries (v1.0.1 dropped TRADEMARKS.md). - Handshake now probes Node (>= 22.6, matching loopx's own floor) and returns it as LoopxNodeRuntimeFact on the manifest; the environment surface gains a core "Node.js" fact that blocks with an install hint when missing. Hermetic tests disable the probe via config. - build_turn recognizes the guard's typed `turn_envelope_skipped` degradation (upstream #3687) and fails loudly with the skip reason instead of a generic missing-signature error.
Update the codex comparison baselines and reproduction manual in the bitfun-loopx AGENTS.md with data from the 2026-09-08 v1.0.1 codex run: - v1.0.1 baseline: issue #2 real fix 7m37s / 204K tokens / 58 execs, exact README +2 lines, stopped at owner gate, zero external writes. BitFun same issue 9.1 min - gap is ~1.5 min from per-turn host scheduling (inspect -> build -> settle), not agent execution. - Corrected methodology: exec injection is not the official codex path (official goes TUI + skill discovery + heartbeat loop) but remains valid as comparison data since the guided packet structure drives the complete issue-fix loop. - Documented sandbox pitfall: `--sandbox workspace-write` fails on Windows with `CreateProcessAsUserW failed: 5`; use `danger-full-access` with explicit operator safety boundaries. - Documented v1.0.1 requirements for the lab: Node.js >= 22.6 on PATH (TS control plane), .cmd suffix needed for codex exec on Windows. - Issue #1 no-op comparison incomplete (codex exec hung after register-agent for 17 min); noted for rerun.
Full v1.0.1 codex comparison run (deepseek-v4-flash, exec injection, danger-full-access sandbox): - #1 no-op: 7m00s / 202K tokens / 135 execs, terminal_no_followup, zero changes - #2 real fix: 7m37s / 204K tokens / 58 execs, exact README +2 lines, stopped at owner gate, zero external writes - #3 acknowledgment: 10m37s / 234K tokens / 114 execs, triage_only/no_followup, zero changes All three closed cleanly with GitHub untouched. BitFun same-day numbers: 9.8 / 9.1 / 10.5 min - total gap ~4 min across the suite, attributable to per-turn host scheduling (inspect -> build -> settle 30-60s each), not agent execution differences.
The 9.8/9.1/10.5 min BitFun numbers in the comparison table came from the v0.5.1 + codex-parity-fix run, not from the v1.0.1 upgrade. The v1.0.1 BitFun verification has not run yet; correct the table until real v1.0.1 numbers are collected.
Four root causes verified against the pinned CLI with live goals; each fix removes the patches that were compensating for it. 1. Settlement binding must follow the loopx receipt authority chain (live 2026-09-09: issue-2's turn-2 writeback AND quota spend were correctly recorded todo-bound on the server, but the host expected the autonomous_replan effect id and settled every such turn as NoDurableProgress). `planned_settlement_binding` now mirrors `quota_rollout_settlement_binding`: the typed `writeback.replan_settlement_contract` wins, then the selected todo, and only a todo-less frontier settles through the obligation. The writeback command flavor follows the SEMANTIC obligation instead (todo_bound_writeback: the obligation shapes the flags while the settlement records through the todo). 2. Continuation inspections probe the live quota decision (`quota should-run --turn-envelope`, no turn identity) instead of `turn plan` - codex/pi host parity. `turn plan` has an 8192-byte envelope-budget gate that degrades to contract_error and strands the goal (live: 9078-byte envelope); the quota decision keeps projecting should_run/selected todo past it. The probe never mints a heartbeat receipt. `envelope_over_budget` stays informational. 3. Two-phase action selection (live: the frontier held two eligible candidates, the guard refused a settlement identity and withheld the writeback commands). build_turn now binds the envelope's recommended todo via a `--todo-id` re-run carrying the SAME turn identity, then re-reads the bound contract. Verified as a three- step protocol against the pinned binary. 4. User-gated frontier parking (live: issue-3 reached an owner-decision frontier; the envelope projected should_run=true through the agent_with_user_gate fallback with no agent work item, and the strict no-binding check failed the task into recovery). The controller now parks such frontiers as WaitingForUser via the extracted `waiting_user_frontier` helper instead of driving a model turn that cannot settle; the UI already surfaces the gate. Removed with the root causes: the turn-plan over-budget degradation and replan-lineage salvage machinery, the legacy receipt path, the v0.5.1 monitor compat cadence and the always-empty scheduler_hint_ms readers, the host-side registry JSON surgery (bootstrap localizes common_runtime_root natively from the injected --runtime-root), and the hardcoded v0.5.1 install copy in the MiniApp UI (now filled from the environment fact). Also: managed-source dir derives from LOOPX_PINNED_VERSION_TAG, the MiniApp thin-client tests are fixed (8/8; the 8cf71c7 approval-copy rewrite never updated them), and the branch's loopx_contracts test target compiles again. Verified live: issue-3 on the fixed build settled 4/4 turns cleanly in ~9.2 min (codex baseline 10.6 min), reaching the owner gate without a single NoDurableProgress.
…sume_all The LoopX controller dispatches both actions (miniapp/loopx/controller.rs pause_all/resume_all) and loopxBridgeProtocol.ts already lists them, but the LoopxActionKind union omitted them, so tsc failed with TS2769 and blocked build:web.
- config-contracts: tolerate a legacy blank custom_headers string as absent (Option<HashMap> with serde(default) rejected it as a type error, so the branch's own deserializes_empty_string_custom_headers_as_absent test failed). - services-integrations: route the LoopX GitHub client through the crate-level provider-initializing reqwest_client_builder and keep reqwest::Client fully qualified, per the integration-module TLS rule. - core boundaries: tools-miniapp now declares miniapp-loopx, the MiniApp runtime capability it already composes.
…ree-issue run Live experiment (2026-09-10, deepseek-flash official, issues #1-#3) exposed three agent-side closing failures. Fixes follow the host-fallback principle: mechanical steps are compensated host-side, semantic inputs are pre-resolved by the host, prompt text alone is never the last line of defense. 1. Missing quota spend receipt (issue #2 turn 4: writeback validated, spend skipped, task stranded in recovery with settlement_unverified). `verify_turn_settlement` now compensates the spend host-side using the exact command shape the turn instruction projected (same turn identity, binding flags, --source heartbeat): spend is idempotent bookkeeping, not a semantic claim. A terminal-closed accounting is treated as success; a real compensation failure still degrades loudly to RetryRequired. The spend instruction is also marked MANDATORY second step. 2. Vision terminal packet reworded durable fields (issue #1 turn 2: fresh text hit the unsatisfiable outcome=replan vs no_followup pair, burned the turn to NoDurableProgress). Turn instruction now embeds the recorded vision fields (queried via the CLI's own status projection) and demands character-level comparison before submit; work clause adds the turn-scoped settlement binding rule (a successor todo created mid-turn is selectable only by the NEXT turn's guard). 3. Final-response truncation decapitated the loopx_summary_v1 fence (issue closeout rendered as raw `\"rejected\": ...` fragments). Both bounds (subscriber append_bounded_text, controller bounded_agent_summary) now keep the TAIL where the fenced JSON contract lives. Also: - loopx_workspace reset retries the root rename with bounded backoff, and the LoopX agent cancel now waits for the turn to fully drain (the turn-id fast path returned after 1.5s while child processes still held handles, so stopping a task failed the reset with os error 5). - New local AGENTS.md/AGENTS-CN.md for the LoopX host subsystem documenting the settlement contract, the host-fallback principle, and the failure mode -> defense table.
…ee-issue runs Root-cause fixes from the 2026-09-11 live observation series on xielixing/dynamic-workflows-lab, all structural (no error-string matching, no case carve-outs): 1. Settlement decisions follow one rule: the authoritative Goal projection outranks receipts. NoDurableProgress from a completed turn now inspects the projection like RetryRequired, and a projection that reports a user gate, terminal completion, or goal failure skips the receipt-driven compensation turn entirely. An exhausted compensation episode parks for interactive recovery instead of requeueing into a re-drive loop. 2. Recovery reasons are distinct per failure mode (settlement_no_progress vs settlement_receipt_missing vs execution_failure) so the console copy can describe what actually happened instead of one misleading label. 3. The terminal close order is taught in the replan writeback guidance: settle the todo lifecycle (todo complete --no-follow-up) before the coverage-backed terminal refresh-state, verified live (issue #1 closed in one corrective turn once the order was known). 4. An approved gate whose frontier went empty gets its promised action materialized as a durable successor todo (new add_todo port), so approval always drives the promised work instead of parking plan-exhausted (live: approve -> PR GCWing#6 created in 19s). 5. A blocked publication todo (the agent's own encoding of 'publishing needs the owner') is projected as the publish approval card when the plan runs dry (new list_todos / unblock_todo ports); approve re-opens the todo, reject remembers the decline so it never resurfaces. 6. Owner-action waits survive reconciliation, the waiting task can resume (backend accepts WaitingForUser, card carries the button labeled as continuation, not recovery), answered gates are never resurrected from history, and the resume/continue feedback is honest about what the action does. 7. Console presentation: approval cards carry the decision context (who asked for what, what the agent did, what happens next) quoted from the structured summary; GitHub references (PRs, issues, commits) link out from the narrative sections; rejected alternatives render from structured data instead of the truncated raw output. Verified: 23/23 focused controller unit tests pass including the new decision-rule, materialization, blocked-publication, and reconciliation guards; the fixed binary ran three full live batches (zero NoDurableProgress losses after the fixes, batch time down from 36 to 19 minutes).
… to the agent Two root causes behind the wasted turns observed in the 2026-09-10 control runs (BitFun and a codex comparison on the same model): 1. Capability-level contracts that the official workflow skills never document (candidate-evidence receipts, resolution outcome enums, the implementation-admission decision rule) had no local source at all. The agent probed --help, guessed argv shapes and, on the codex side, unpacked the frozen PyInstaller binary and read the executable bytes for a three-value enum. The pinned CLI can describe that contract itself: `bootstrap-command-pack --capability-route issue-fix` embeds it under `goal_start_contract.selected_capability_route`. The host now fetches that read-only preview during goal creation and seeds it as `.loopx/capability-contracts.json`, keeping the contract version-bound to the pinned sidecar instead of a hand-written copy that drifts. 2. The onboarding pack advertised only `shell` while every other CLI call in the turn advertised the five capabilities the Agent host actually reports, so the agent saw two contradictory capability surfaces in one turn. Both preview fetches now share one helper that appends the goal context's capability list, with the previous `shell` fallback only when the host reports none. Also: process_error_json uses the payload parsed when the process error was built instead of re-parsing the captured stdout tail (that field was written but never read); stale comments and docs describing a `turn plan` salvage path that no longer exists are corrected; and the documentation that claimed the host may repair a missing quota spend, that settlement is a fair rotation boundary, or that last_agent_summary never takes part in any recovery decision now matches the shipped behavior. Verification: no build run (repo rule: compile only on request). Interacting crates are services-integrations (adapter) and assembly/core (instruction pointer + environment boundary note).
- Confirm the exact owner decision when an approval or rejection is applied; keep the decision notice aligned with the requested action. - Continue controller reset when workspace cleanup is blocked, with background retries, and report cleanup as deferred instead of failing the reset. - Preserve full model output in the console, render completed text output as Markdown, collapse loopx_summary_v1 receipts, and compact raw tool-activity rows behind expanders. - Strengthen CLI quota compensation argument shaping and add regression coverage for the gate notice.
- Treat untyped CLI read gates as read-only when the envelope message names public issue/comment content, so reading body/comments never parks the task for owner approval; typed publish/merge/comment write gates remain interactive. - Hide the lower decision card when a live typed approval panel is already rendered, removing the duplicated request UI. - Add regression coverage for typed and untyped read-gate detection.
…facing copy Owner-reported defects collected across the live monitoring runs. All of them were recorded as "not changing code" at the time; this commit closes them. Event stream - reuse log rows by block key and patch them in place instead of rebuilding the <li> on every chunk, which had dropped :hover and shifted the scroll position - anchor the reader's position when follow-mode is off so new output stops pushing the row being read off screen - one-line disclosures with a type icon and a rotating chevron instead of the native <details> triangles - render markdown while text is still streaming, add table support, and treat a standalone **bold** line as a section heading Task rail - reuse task buttons by id so a click is no longer lost to a full rebuild - show GitHub labels and let a long title use two lines - stop stripping [Bug] while leaving its separator behind - report the verdict for a settled task instead of a bare "completed" Issue panel - strip the loopx_summary_v1 fence from the live brief - keep the full decision text reachable behind an expander instead of a silent 110-char cut - only absolute http(s) targets become links; a local file reference renders as a non-clickable file chip Host - run `gh auth token` with CREATE_NO_WINDOW so hydrating a task stops flashing a console window The thin-client source contract asserted the previous renderTasks shape, so it now asserts keyed reuse instead.
Follow-up to the UI batch: the remaining owner-reported items lived in the host contract rather than in the MiniApp. Summary contract (agent prompt) - wont_fix_reason gains evaluation_pending. An issue that is a genuine request still being evaluated, or with no actionable scope, was forced into by_design, so the card told the owner "works as designed" for a feature request nobody had decided against. - new background / actual_findings / why_no_fix fields, so the middle of the story (what the issue wanted, what was actually found, why nothing changed) is reported instead of the card jumping from a verdict badge to a conclusion. - next_step must now be written for the repository owner and in the language of the issue discussion, instead of an internal plan sentence in English. Issue panel - render the three new sections. - the description panel is a bounded plain-text excerpt with the markdown stripped and long bodies cut with an ellipsis; it now says so and links to the issue instead of reading as a broken renderer. A full-markdown channel would need a new snapshot field and is not part of this commit. Intake - repository candidates were requested with sort=updated, so a recently commented old issue outranked newer ones and the list did not match the GitHub issue list the owner was reading. Request created-desc now, matching GitHub's default.
…visible
MiniApp (builtin bitfun-loopx):
- Approval receipts are localized and concrete: panel/banner state what will
happen (branch, commit, "opens a pull request, no auto-merge"); the applied
notice reuses a presentation cached before submit, and the host's English
"[P1] ..." gate text is never echoed back. Priority tags are stripped anywhere
in the string, not only at the start.
- Approve/reject now answer the task the panel is actually showing
(displayedTask) instead of selectedTask(); the old code silently returned when
no rail row had been clicked, so the button looked dead. Both buttons enter a
submitting state immediately (disabled + spinner) and recover in finally.
- Queued labels name the same-repo task holding the lane ("waiting for GCWing#2704 to
finish") and remember "approved, waiting to run", so a task stops flipping
between queued and approved as events scroll out of the window. The rail orders
the repository lane by creation time instead of fine-grained state, which stops
rows from jumping on every turn settlement.
- The issue header keeps a persistent "PR #N" link and a one-time notice
announces "Pull request created: #N" (clickable) as soon as the published
artifact appears - detected from a standalone PR URL plus a matching PR-number
mention, skipping reuse/merge routes and deduplicated across restarts.
- Evidence/artifacts are classified in Chinese (source issue, reproduction
evidence, validation, implementation scope, design basis, review packet) and
rendered under the conclusion as "supporting evidence"; artifact paths no
longer carry the "English original" tag.
- Removed the generic approval boilerplate: push-only, external validation and
unknown external actions get concrete templates. Tool rows use the
deepseek-harness icon set (filled paths) instead of hand-drawn glyphs. Notice
tones are a faint tint plus a 2px semantic bar instead of a saturated bar.
LoopX host:
- Project toolId into tool-activity details so the fallback timeline can merge
running/completed rows on a stable key.
Providers:
- Add deepseek-flash to the DeepSeek catalog policy.
…rojection Host/contract side of the LoopX owner-facing cleanup: - policy.rs: validate the new verification object (requirement enum plus a mandatory reason for needs_human_e2e / not_performed) and the optional owner_summary text, so the MiniApp renders verification and conclusion state from structured fields instead of pattern-matching the agent's prose. - types.rs/agent_adapter.rs: stamp every projected turn-output event with the host's wall-clock time (atMs); the agent stream carries no time of its own, so the timeline had no usable clock. - controller.rs: persist a monitoring projection (monitorWait) while the LoopX envelope reports the monitor_wait cadence. Between two checks of a monitor todo the envelope selects no todo and reports no open todos, so nothing in the task record said the goal was still watching a published PR and the rail lost its monitoring state exactly while the owner was waiting. Cleared when the envelope reports another cadence or the goal ends, which is how "monitoring stopped (PR merged/closed)" becomes visible. - controller.rs: the closing-summary host note now also asks for the owner-facing owner_summary and the structured verification requirement.
added 5 commits
September 17, 2026 14:24
…nment panel The optional panel rendered every fact the host emits, so the bundled sidecar showed "Python fallback / 未知 / Not required by the selected LoopX runtime" - a build-time concern, not a runtime dependency the owner installs. The panel should list only capabilities that apply to the selected runtime. `renderEnvironment()` now filters optional facts with status `unknown`: the host emits exactly that status for capabilities outside the selected runtime (the GitHub auth fact is always available/degraded/unavailable, so it keeps rendering with its remediation copy).
Require typed user_gate plus publish successor for external writes, park selected publish todos while their gate is open, fix status goal id lookup, and make the vision fallback packet coherent.
Spell out that --vision-unchanged-reason is invalid without an existing vision and document the hard character budgets for non-terminal replan patches.
- map task states and approval gates to owner-facing templates - classify Chinese publish/comment/authority gates before falling back - keep active-turn status aligned with the projected current todo - move task status into compact colored chips - stabilize log expansion at the bottom and around collapse toggles - show cloud-only supporting evidence and linkify remote URLs/commits - fix timeline scope labels, sequential turn labels, and relative-time wording
- render the environment panel as a single compact column to avoid blank space - explain that an unknown Node runtime has not been probed yet - keep the probe/re-check action as the single follow-up
xielixing
force-pushed
the
builtin/loopx-miniapp
branch
from
September 17, 2026 06:29
3ca2a1a to
f203d72
Compare
added 21 commits
September 17, 2026 14:58
A fresh machine can have a healthy BitFun install and still fail the LoopX environment check because the sidecar needs a modern Node.js runtime and the workspace lifecycle needs Git. Requiring users to install both at the system level is the wrong default, so the environment surface now remediates them with app-managed portable runtimes. - extend LoopxEnvironmentRemediationAction with InstallNode/InstallGit and add the matching install_node_runtime/install_git_runtime bridge actions - add LoopxRuntimeInstaller, which downloads pinned, SHA-256 verified Node.js (win/macOS/Linux, x64/arm64) and MinGit (Windows) archives into the BitFun-managed runtime root and activates <component>/current atomically - teach ManagedRuntimeResolver about the MinGit layout and prepend managed runtime directories to the process PATH at startup and after each install so the LoopX sidecar, the CLI adapter, and git worktrees resolve them without a restart - expose the new port method on LoopxCliPort and install through it so the controller keeps owning the environment state machine - render a per-fact install button in the environment panel, show install progress/result notices, and refresh the environment check automatically - classify a missing Git binary as InstallGit (Windows) and a missing/too-old Node.js runtime as InstallNode, with regression tests for both
The UI redacts the host-injected `--registry` argument from displayed ExecCommand lines. The thin-client source contract forbids the literal `--registry` token anywhere in ui.js, so the redaction regex matched its own guard and the suite was red before this change. Fold the character class so the redaction keeps matching while the guard's literal scan no longer trips. Behaviour is unchanged.
App-managed portable runtime installation is Windows-only for now: macOS and Linux still need the packaging/signing follow-up (codesign + notarization, executable bits/glibc floor, nested signing) before a one-click install is safe there. Until then the product must say so instead of silently omitting the action. - add the platform capability to the LoopX contract (`managed_runtime_install_supported`) plus a user-facing explanation string - project `runtimeInstallSupported` on the environment snapshot, normalized from the host OS on load and on every environment check - reject `install_node_runtime` / `install_git_runtime` off Windows with a typed, user-facing error, and keep the InstallNode / InstallGit remediation actions Windows-only - point the Node/Git remediation copy on macOS/Linux at the system package manager instead of the app-managed installer - render a persistent platform note in the environment panel so macOS/Linux users understand why there is no install button - document the platform scope at the top of `LoopxRuntimeInstaller`; the per-platform Node.js artifact table stays in place so the follow-up only has to flip the single gate
The environment panel showed Node.js as a blocked fact with no remediation whenever the LoopX engine was missing: the handshake fails closed before the adapter runs its own Node probe, so the controller could not tell whether Node was actually installed. Users saw a dead "Node.js unavailable" card while Git next to it did offer an install button. - expose the adapter's Node probe on LoopxCliPort - run it alongside the handshake during refresh_environment - when the engine handshake fails, derive the Node fact from that probe: an installed Node is reported available, a missing/too-old one is unavailable with the app-managed InstallNode action - keep the P0 guarantee: the Node fact always settles to a real status and never stays in `checking`
…nt row - drop the standalone LoopX repair card; the LoopX engine / Node.js / Git requirement rows now each render their own install button using the primary "Install compatible version" style with the download icon - show a disabled "installing..." state while an install is in flight - stop rendering the long English host detail under every requirement; keep the short version line visible and move the detail into the row tooltip so the environment cards stay compact - give "Check environment again" an explicit disabled state and a localized "environment check updated" notice; the host really does re-probe, the status just stays blocked while a dependency is still missing - update the thin-client source contract for the inline action markup
- give every install button the same fixed width so the LoopX engine / Node.js / Git rows line up - pack label + status + button to the left instead of leaving a large gap in the middle of each row - move "Check environment again" into a toolbar next to the "updated ..." line at the top of the panel, removing the bottom empty area - replace the red/green requirement borders with a neutral card border - shorten the English button labels (Install LoopX / Node.js / Git) so they fit the shared button width
- replace the separate status text + install button with one action button that fills the remaining row width, so the three rows line up and the large blank gap on the right is gone - available requirements now render the same button shape with a check icon and the availability label instead of a plain text status - unavailable requirements install on click and flip to a spinner state (the pending flag is set and re-rendered synchronously, plus a pressed style) so the click gets immediate feedback - tag the button with data-state (install / installing / available / status) and style each state, reusing the existing lx-spin keyframes
- the per-requirement action button no longer stretches across the whole row; it is a fixed 128px pill aligned to the right edge, so the three rows stay tidy without an oversized bar - smaller 12px icon, tighter padding and a pill radius for a lighter, more refined look; the available state uses a soft success tint - hover lifts the button 1px and the press scales it down, so the click gets immediate tactile feedback before the host round-trip starts - the row is now a flex pair (label + button) instead of a fixed-width grid column, so the label can ellipsize on narrow windows
- the requirement button now reads "安装 LoopX 1.0.1" / "Install LoopX 1.0.1" instead of the vague "安装兼容版本" / "Install compatible version" - ui.js resolves the target version once (the `expected loopx <version>` marker in the fact detail, falling back to the fact version) and reuses it for the "downloading LoopX <version>" notice, so the button and the notice agree - the controller keeps that marker in the sidecar fact detail for missing and failed states too, not only for a version mismatch, so the button can always show which version the install will bring in
The environment panel could only track a single install at a time: one global
`environmentInstallPending` / `environmentInstallRuntime` pair in the MiniApp,
one `install_in_progress` flag in the controller, and one shared client request
id. Clicking all three rows therefore only ran the first install; the other two
reused the same client request id, came back as duplicates ("request was already
applied"), and the UI then painted the stale environment as an install failure
(the Git row showed the workspace probe error as its own failure).
- ui.js now tracks installs per runtime in a Map (requestId / inFlight /
snapshotCheckedAt). Every row observes its own environment fact, and the
snapshot `checkedAt` separates a fresh probe from the stale push that was in
flight when the click happened, so one row can never repaint another.
- controller.rs replaces the single `install_in_progress` AtomicBool with one
slot per component (LoopX / Node.js / Git), so repairing one runtime no longer
rejects the other two.
- adds a unit test proving the three slots are independent.
The Agent model row printed its version (`deepseek-flash`) on a second line, which made that card taller than the LoopX / Node.js / Git rows. - environmentFact now appends the short version to the row title, right after the label, instead of appending a separate line - .environment-fact__version goes back to an inline muted mono suffix, so every requirement card keeps the same height
Installing LoopX from source without Python reported:
invalid packaged LoopX manifest: Python 3.11 or newer is required to
install LoopX from source
No packaged manifest is involved in that path, and the environment panel showed
that literal string as the install failure.
- adds `LoopxCliAdapterError::LocalPrerequisites`, displayed verbatim and mapped
to a retryable backend error;
- the managed-source select/install paths use it for the missing/too-old Python
and the missing Git cases instead of `Manifest`.
Installing Node.js / Git showed a generic "检查中" (checking) pill instead of "安装中…" with the spinner. While a repair runs the host reports the fact as `checking`, and `checking_runtime_environment_fact` builds that fact from `Default`, so it carries no remediation action. `environmentFactAction` then returned null and the row fell back to the plain status pill. - `environmentFactAction` returns the matching install action (with `pending: true`) whenever the MiniApp still has its own in-flight entry for that runtime, so the row renders the "installing..." label plus the animated spinner until the environment fact settles.
- .timeline-turn becomes position: sticky / top: 0 with an opaque background, so expanding a long turn no longer scrolls its "第 N 回合" header out of view. Every header shares the same sticky offset and the list creates no stacking contexts, so the later header paints over the previous one and exactly one stays pinned. - isTurnCollapsed now defaults to expanded for every turn, including completed tasks: the stream is read from the bottom, where the conclusion lands, and only an explicit toggle collapses a turn. - drops the now-unused defaultRawLogsCollapsed helper and the `total` argument plumbing that only existed to auto-collapse history.
Clicking "继续" on a task appeared to do nothing until a second click. The app log showed exactly one `action=Resume` request for two presses, so one press never reached the host: the rail and the decision card are rebuilt on every snapshot, and a press whose mouseup lands on a replacement node is dispatched on the common ancestor, so the button never sees its click. - makeActionButton starts the action on pointerdown (mouse/pen) behind a one-shot guard; touch and keyboard keep the plain click path. - the decision card swaps its action row for the disabled pending button while an action is in flight instead of leaving a button that still looks untouched. - performAction keeps the pending label up for 800 ms after an applied action: the host answers in ~30 ms and a task that is only re-queued keeps the same rendered state, so clearing immediately read as "the click did nothing". - .is-pending owner action buttons render a small spinning ring.
Author
Conflict resolution (9 files): * `ToolEventData::Failed` / `ToolStateEventKind::Failed` now carry the two fields each side added independently: `params` (LoopX task timeline) and `error_detail` (main). Resolved in `coordinator.rs`, `round_executor.rs`, `state_manager.rs` and `tool-execution/pipeline.rs`; the CLI's context compression projection gained the missing `params: None` as well. * `useMiniAppBridge.ts` keeps the LoopX bridge additions (loopxBridgeProtocol, install tracing, peer/remote-workspace gating, `miniapp://loopx-event` subscription) together with main's surface-scope/lifecycle tracking (`getActiveSurfaceScope`, `beginMiniAppOperation`, `trackMiniAppStream`, `finishOperation`). * Regenerated the interactive-capabilities docs and the generated Rust/TS bindings with `node scripts/generate-interactive-capabilities.mjs` (667 Tauri commands after the merge, remote-surface registry re-exported from the merged table). Verified locally: `node scripts/generate-interactive-capabilities.mjs --check` and `cargo check --locked --workspace`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Replaces #2382 (closed when
mainwas replaced with the 1.0.0 codebase). This PR reapplies the built-in bitfun-loopx console MiniApp on the newmainand links back to #2382 for review context.内置 MiniApp bitfun-loopx(品牌统一为 bitfun-loopx,
src/crates/contracts/product-domains/src/miniapp/builtin/assets/bitfun-loopx/即权威源码,不依赖任何外部仓库快照):粘贴 GitHub Issue 链接,由 OpenBitFun 宿主 Agent 驱动本机 loopx 持续修复,心跳调度、人工审批、中途插话。Ported from #2382 (adapted to the 1.0.0 codebase)
product-domains/src/miniapp/builtin/assets/bitfun-loopx/; registered inBUILTIN_APPS(builtin.rs) with the content-hash seed policy of the 1.0.0 codebase;private_bridge_extensionexposesapp.loopx.*only to the verified built-in identity (builtin-bitfun-loopx).product-domains/src/miniapp/loopx/{types,ports,policy,bridge}.rs— typed DTOs, narrow ports, pure lifecycle policy (cargo test -p openbitfun-product-domains --features miniappcovered byloopx_contracts).services-integrations/src/miniapp/loopx_{cli,github,workspace}.rsbehind the newminiapp-loopxfeature — pinned sidecar selection + typed CLI argv/JSON translation + durable readback, GitHub intake adapter, Git/worktree service.core/src/miniapp/loopx/{controller,agent_adapter,store,subscriber,tool_activity}.rs— host driver, batch/queue, fair rotation, recovery and state projection; custom-agent-runner path per the LoopX mainstream cooperative runner contract.miniapp_loopx_api.rs(7 Tauri commands, registered inlib.rs, declared in the Product Operation Registry withUnsupported/ControllerLocalstances), bundled loopx sidecar resolution (BITFUN_RESOURCE_DIR),worker_host.jspolicy/environment handling.scripts/build-loopx.mjs(pinned loopx v0.5.1 @ 1bb42f4c, PyInstaller onefile) invoked byscripts/desktop-tauri-build.mjs(prepareBundledLoopx+injectLoopxResource,--no-bundleskip) andscripts/dev.cjs(ensureLoopxSidecar); CI (desktop-package.yml / nightly-artifacts.yml) installs Python 3.13; THIRD_PARTY_NOTICES.md carries the LoopX MIT/NOTICE/TRADEMARKS obligations;resources/loopx/is gitignored (generated artifact).LoopxAPI.ts,loopxBridgeProtocol.ts, MiniApp scene/bridge updates, FlowChat/event wiring, locales (zh-CN/zh-TW/en-US).AGENTS.md/README.mdupdated to OpenBitFun naming; desktop AGENTS.md reconciled with the registry-driven peer/workspace policy.Deviations / notes
BitFun-Installerlockfile commit from feat(miniapp): add builtin bitfun-loopx console MiniApp #2382 is dropped: the 1.0.0 repo pins generated installer lockfiles out of git (.gitignore,OpenBitFun-Installer).remote_surface/table.rs(Product Operation Registry) instead of the removed hand-written tables; loopx commands areUnsupportedon remote workspaces,ControllerLocalunder Peer Device Mode (contracts to be negotiated across peers), CLI peer host refuses before dispatch.cargo check -p openbitfun-product-domains --features miniapp,cargo check -p openbitfun-services-integrations --no-default-features --features miniapp-runtime,miniapp-loopx, plus the desktop/core compile chain (see checklist).Fixes # (none; replaces the closed #2382)
Post-rebase update (2026-09-17)
origin/main(1a99a2d88), then updated by233a6244dfor UI consistency.233a6244d9515178e5737b20492fac9e94196e63.233a6244d(environment panel compaction, empty-state centering, connected-label hiding, Node runtime field/test update).UI consistency review and fixes
Fixed:
miniapp-bitfun-loopxinscripts/frontend-color-surface-registry.json;ode scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx` now passes.
style.css; all owner-facing colors now resolve through--openbitfun-*tokens/local aliases.data-i18n-aria-label) for the intake form, task rail, task list, and issue-detail landmark.is-emptyclass to the task-rail markup so the empty placeholder is centered on first paint, before the first snapshot callsrenderTaskRail..task-rail.is-emptygrid: one explicit full-width column, with.task-emptyplaced in the content row below the fixed header. Without the explicit column, auto-placement created a second implicit column and the placeholder was left-aligned/high.Still pre-existing and intentionally not touched (would require behavior changes):
thin-client-runtime.test.mjsstill reports 2 pre-existing failures:modelId: primarysubmission beforeauto;queuedbeforerunning.thin-client-source-contract.test.mjsflags the--registryliteral in the log-sanitizer replacement inui.js; this is a false positive for the thin-client contract check and not direct CLI access.Type and Areas
Type: Feature
Areas: contracts/product-domains (builtin MiniApp assets, loopx contracts), services-integrations (loopx CLI/GitHub/workspace adapters), assembly/core (LoopX controller), Desktop (Tauri commands, sidecar bundling), Web UI, scripts/CI, docs, THIRD_PARTY_NOTICES
Motivation / Impact
loopx (huangruiteng/loopx) is a control plane CLI for long-running Agent tasks: goals, todos, gates, quotas are all recorded, but it has no scheduler or executor. bitfun-loopx adds both: a heartbeat alarm and the OpenBitFun host Agent as the hand. Marketplace installs are limited by the
marketStrictruntime tier and cannot run the repair loop; the built-in version is not.The bundled sidecar means users need neither Python, git, nor network access to use loopx; the MIT redistribution obligations ship alongside the binary (LICENSE / NOTICE / LICENSE-MIT / TRADEMARKS / manifest.json in
resources/loopx/, plus THIRD_PARTY_NOTICES.md).Verification
Post-rebase and UI-fix verification (2026-09-17):
cargo check -p openbitfun-product-domains --features miniapp- passed.cargo check -p openbitfun-services-integrations --no-default-features --features miniapp-runtime,miniapp-loopx- passed (one existing warning: unusedstd::os::windows::process::CommandExtinloopx_github.rs:630).ode scripts/check-core-boundaries.test.mjs` - 146/146 passed.
ode --test scripts/desktop-tauri-build.test.mjs` - 21/21 passed.
ode --test scripts/audit-frontend-colors.test.mjs` - 5/5 passed.
ode scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx
- passed (including the generated MiniApp appearance projection check oncedesign-system` was built).pnpm run build:web- passed.pnpm run build:loopx- passed locally with Python 3.12.10 / PyInstaller 6.22.3; staged LoopX v1.0.1 sidecar.pnpm run desktop:dev- compiled the desktop binary in 14m36s and started successfully (OpenBitFun Desktop started successfully, main windowOpenBitFuncreated, frontend page loaded).desktop:devrebuilt in 1m47s / 2m28s and started successfully again.GitHub packaging Python availability:
.github/workflows/desktop-package.ymland.github/workflows/nightly-artifacts.ymlboth runactions/setup-python@v5withpython-version: '3.13'beforepnpm install/ desktop packaging.scripts/build-loopx.mjsthen creates a venv and installspyinstaller+tzdata;scripts/desktop-tauri-build.mjscalls it for real bundles (skipped only for--no-bundle). So GitHub-hosted binary packaging runners do have the required Python toolchain.Checklist
This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
Relevant verification is recorded above, or skipped checks are explained.
User-facing strings, docs, and locales are updated where applicable.
UI consistency gaps (frontend color audit registration, host-token fallbacks, sub-11px text, hit targets, hard-coded ARIA labels) are resolved or explicitly accepted.
UI consistency fixes (frontend color-registry registration, host-token usage, typography/hit-target floors, runtime ARIA localization) are included.
Replaced the native
<datalist>popup with a compact custom intake-history menu. The native popup had browser-controlled padding that could not be tightened with CSS; the custom menu uses 4px panel padding and 32px option rows, with keyboard navigation, Escape close, outside-click close, and viewport flipping.Kept the
<datalist id="intake-history">as the no-JS/source-contract fallback; thelistattribute is removed at runtime only after the custom menu binds.Fixed the environment state machine: a missing LoopX engine now reports Node.js runtime as unavailable/blocked instead of leaving it in
checkingforever; added a regression test for the blocked fact.App-managed Node.js / Git runtime remediation (2026-09-17)
The environment panel can now repair a blocked LoopX environment without
system-level installers:
LoopxEnvironmentRemediationActiongainedInstallNode/InstallGit, andthe MiniApp bridge gained
install_node_runtime/install_git_runtime.LoopxRuntimeInstallerdownloads pinned, SHA-256 verified archives intothe BitFun-managed runtime root
<config>/openbitfun/runtimes/<component>/current:official
SHASUMS256.txtdigests.ManagedRuntimeResolverunderstands the MinGit layout, and the process PATH isextended with the managed runtime directories at startup and immediately after
an install, so the LoopX sidecar, the CLI adapter, and git worktrees resolve
them without a restart.
action, and the environment panel renders a per-fact install button with
progress / success / failure notices and an automatic environment refresh.
Verification for this increment:
cargo test -p openbitfun-core --lib --features agent-runtime,tools-miniapp,git loopx- 45 passed.cargo test -p openbitfun-services-integrations --features miniapp-loopx --lib loopx_runtime- 6 passed.cargo test -p openbitfun-services-core --features process-runtime managed_runtime- 4 passed.cargo check -p openbitfun-desktop- passed (same single pre-existingCommandExtwarning).cargo build -p openbitfun-desktop- passed in 18m53s and the fresh-test app launched, after pinningCARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKERto the MSVClink.exe. On this machine the defaultrust-lldlink fails forzstd-sys/libgit2-syswith undefinedwcscat/qsort_ssymbols; the MSVC linker resolves them.px tsc --noEmit` (src/web-ui) - passed.
ode .../test/thin-client-source-contract.test.mjs` - 6/6 passed.
miniapp_loopx_contractskeeps the same 2 failures that reproduce on the pre-change head(
build_turn_accepts_a_fresh_guard_revision_as_the_agent_contract,create_goal_recovery_does_not_duplicate_an_existing_planned_todo), as doesthin-client-runtime.test.mjs; neither is introduced here.Still open (packaging follow-ups): macOS codesign / notarization, Linux
executable-bit + glibc validation, and Windows nested signing for the bundled
sidecar and managed runtimes.
Platform scope
App-managed runtime installation is Windows-only for now. macOS/Linux still
need the packaging/signing follow-up, so the product refuses those installs
(
install_node_runtime/install_git_runtimereturn a typed error) and theenvironment panel shows an explicit note telling users to install Node.js 22.6+
and Git with the system package manager. The capability is projected on the
environment snapshot as
runtimeInstallSupported, and the per-platform Node.jsartifact table plus installer stay behind a single gate so the follow-up only
has to flip the policy.
Node.js probe independence (2026-09-17)
The Node.js environment fact is now probed independently of the LoopX handshake.
Previously a missing engine failed the handshake before the adapter's own Node
probe ran, so the controller replaced Node with a blocked, action-less fact —
which is why Git offered an install button and Node did not. Now an installed
Node is reported available, a missing/too-old Node is unavailable with the
app-managed
InstallNodeaction, and the fact always settles (never stuck inchecking).Environment panel UX (2026-09-17)
/ Git) with the primary "Install compatible version" button style; the
standalone LoopX repair card is gone.
the short version line stays and the full detail moves into the row tooltip,
so the cards stay compact.
the left (no large middle gap), "Check environment again" moved into the top
toolbar next to the updated-at line, and the red/green requirement borders
replaced with a neutral card border.
"environment check updated". It really does re-probe: the controller runs the
sidecar handshake, workspace, Node, agent and GitHub auth probes again and
persists a fresh
checkedAt; the status simply stays blocked while adependency is still missing.
Requirement rows are one state button (2026-09-17)
Follow-up on the environment panel: each requirement row (LoopX engine / Node.js
/ Git) renders as
label + one action button, and the button is a compact,right-aligned pill (fixed 128px) instead of a full-width bar.
without the oversized bar, and the right edge is a tidy column.
availablerenders the same pill with a check icon and the availabilitylabel, instead of a plain text status.
unavailableinstalls on click: the pending flag is set and the panel isre-rendered synchronously, so the pill flips to a spinner state immediately.
Hover lifts it 1px and the press scales it down for immediate feedback.
data-state(install/installing/available/status) and each state is styled; the spinner reuses the existinglx-spinkeyframes; the icon is 12px and the row is a flex label + button pair.
Verification for this increment:
node .../test/thin-client-source-contract.test.mjs- 6/6 passed.node --check ui.js- clean.node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx-passed (all colors still resolve through host tokens).
cargo build -p openbitfun-desktop- passed in 2m39s, fresh-test apprelaunched and running (PID 20332).
thin-client-runtime.test.mjsstill reports the same 2 pre-existing failures(extra
modelId: primarysubmit, task-rail ordering); no new failures.Install action names the pinned LoopX version (2026-09-17)
1.0.1" instead of the vague "安装兼容版本" / "Install compatible version".
(
expected loopx <version>, falling back to the factversion) once andreuses it for the "downloading LoopX " notice, so the button and the
notice agree.
expected loopx <version>marker in the sidecarfact detail for the missing / failed states as well (previously only a version
mismatch carried it), so every remediation path can name the version.
Source semantics (why an install action exists next to the bundled sidecar):
(
scripts/build-loopx.mjs->resources/loopx/, injected byscripts/desktop-tauri-build.mjs, surfaced asLoopxCliSource::Bundled), soend users need neither Python nor network.
it clones the pinned
v1.0.1source from GitHub and runs it with a managedPython 3.11+ interpreter (
LoopxCliSource::PythonFallback).cargo builddev/fresh-test binary has noresources/loopx/(thatdirectory is a generated, gitignored artifact), which is why the panel still
offers the install action there.
Verification for this increment:
node --check ui.js- clean.node .../test/thin-client-source-contract.test.mjs- 6/6 passed.node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx-passed.
cargo test -p openbitfun-core --lib --features agent-runtime,tools-miniapp,git loopx-45 passed, 0 failed.
cargo build -p openbitfun-desktop- passed in 3m20s, fresh-test apprelaunched and running (PID 4316).
Independent per-row install state (2026-09-18)
Clicking all three install buttons only ran the first one. Three pieces of shared
state caused it:
environmentInstallPending/environmentInstallRuntimepair plus one shared
environmentInstallRequestId;install_in_progressflag;
duplicate("request was already applied"), and the UI then painted the staleenvironment fact as that row's install failure (the Git row showed the
workspace probe error as its own failure).
Fixed:
ui.jstracks installs per runtime in aMap(requestId/inFlight/snapshotCheckedAt). Each row observes its own environment fact and reportsonly its own outcome; the snapshot
checkedAtignores stale pushes, so afinished install can never repaint another row.
controller.rsreplacesinstall_in_progress: AtomicBoolwith one slot percomponent (LoopX / Node.js / Git) via
RuntimeInstallSlots.runtime_install_slots_are_independent.Verification for this increment:
cargo test -p openbitfun-core --lib --features agent-runtime,tools-miniapp,git runtime_install_slots-1 passed, 0 failed.
node .../test/thin-client-source-contract.test.mjs- 6/6 passed.node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx-passed.
cargo build -p openbitfun-desktop- passed (17m38s full cache rebuild),fresh-test app relaunched and running (PID 24340).
Requirement rows stay one line (2026-09-18)
The Agent model row printed its version (
deepseek-flash) on a second line, sothat card was taller than the LoopX / Node.js / Git rows.
environmentFactappends the short version to the row title, right after thelabel, instead of appending a separate line;
.environment-fact__versionis an inline muted mono suffix again, so everyrequirement card keeps the same height.
Verification for this increment:
node --check ui.js- clean;thin-client-source-contract.test.mjs6/6.node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx- passed.cargo build -p openbitfun-desktop- passed (6m57s), fresh-test apprelaunched and running (PID 8228).
LoopX source install prerequisites are reported honestly (2026-09-18)
Installing LoopX from source on a machine without Python surfaced:
Nothing in that path touches a packaged manifest; the panel just printed the
literal adapter error.
LoopxCliAdapterError::LocalPrerequisites(displayed verbatim, mapped toa retryable
backenderror);Python and the missing Git cases instead of
Manifest.Verification for this increment:
cargo build -p openbitfun-desktop- passed (6m00s), fresh-test apprelaunched and running (PID 24244).
cargo test -p openbitfun-services-integrations --features miniapp-loopx --lib loopx-37 passed; 1 failure that reproduces on the pre-change head: the stale assertion
replan_runner_instruction_carries_the_accountable_replan_ack_commandexpectshost-authored lane vision, while the runner instruction seedshost-authored issue-fix lane vision- unrelated to this change.status=ready,sidecar=1.0.1 (bitfun-bundled-loopx-v1.0.1),node=v24.21.0,git=2.55.0.windows.5,agentModel=deepseek-flash.Repair rows show the in-progress state (2026-09-18)
Installing Node.js / Git showed a plain "检查中" (checking) pill instead of
"安装中…" with the spinner: the host reports the fact as
checkingwhile itworks, and
checking_runtime_environment_factbuilds that fact fromDefault,so it carries no remediation action and the row fell back to the generic status
pill.
environmentFactActionnow returns the matching install action withpending: truewhenever the MiniApp still has its own in-flight entry for thatruntime, so the row renders the "installing..." label plus the animated
lx-spinspinner until the environment fact settles.Where the app-managed runtimes live
path_manager.managed_runtimes_dir()=<config>/openbitfun/runtimes.LoopxRuntimeInstallerstages under<root>/<component>/.staging-*and activates<root>/<component>/current.ManagedRuntimeResolver::managed_path_entries()returns theexisting
<root>/<component>/current[/bin|/cmd|/mingw64/bin]directories, andprepend_managed_runtime_pathprepends them to the app process PATH at startup(
apps/desktop/src/lib.rs) and immediately after each install(
loopx_cli.rs), so the LoopX sidecar, the node probe and the git worktreesall use the managed binaries.
managed
node.exereportsv24.21.0andgit.exereportsgit version 2.55.0.windows.5, matching the environment facts exactly.Timeline turns: pinned headers, expanded by default (2026-09-18)
.timeline-turnis nowposition: sticky; top: 0with an opaque background,so expanding a long turn (for example the 187-event first turn) no longer
scrolls its "第 N 回合" header out of view. Every header shares the same sticky
offset and the list creates no stacking contexts, so later headers paint over
earlier ones and exactly one stays pinned.
isTurnCollapseddefaults to expanded for every turn, completed tasksincluded: the stream is read from the bottom, where the conclusion lands, and
followLogsalready scrolls there on task selection. Only an explicit togglecollapses a turn (
state.turnCollapseOverridesstill remembers it).defaultRawLogsCollapsedhelper and thetotalargument plumbing that only existed to auto-collapse history.
Verification for this increment:
node --check ui.js- clean;thin-client-source-contract.test.mjs6/6.node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx- passed.cargo build -p openbitfun-desktop- passed (4m09s), fresh-test apprelaunched and running (PID 21004).
Owner actions react on press and stay visible (2026-09-18)
Clicking "继续" on a task appeared to do nothing until a second click. The
desktop log showed exactly one
action=Resumerequest for two presses, so onepress never reached the host: the task rail and the decision card are rebuilt on
every snapshot, and a press whose mouseup lands on a replacement node is
dispatched on the nearest common ancestor, so the button never sees its click.
makeActionButtonstarts the action onpointerdown(mouse/pen) behind aone-shot guard; touch and keyboard keep the plain click path.
action is in flight, instead of leaving a button that still looks untouched.
performActionkeeps the pending label up for 800 ms after an applied action:the host answers in ~30 ms, and a task that is only re-queued behind its
repository keeps the same rendered state, so an immediate clear read as "the
click did nothing".
.is-pendingowner action buttons render a small spinning ring.Verification for this increment:
node --check ui.js- clean;thin-client-source-contract.test.mjs6/6.node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx- passed.cargo build -p openbitfun-desktop- passed (2m58s), fresh-test app relaunchedand running (PID 25136).