Skip to content

feat(miniapp): add built-in bitfun-loopx console MiniApp (replaces #2382) - #2836

Open
xielixing wants to merge 58 commits into
GCWing:mainfrom
xielixing:builtin/loopx-miniapp
Open

xielixing wants to merge 58 commits into
GCWing:mainfrom
xielixing:builtin/loopx-miniapp

Conversation

@xielixing

@xielixing xielixing commented Sep 6, 2026

Copy link
Copy Markdown

Summary

Replaces #2382 (closed when main was replaced with the 1.0.0 codebase). This PR reapplies the built-in bitfun-loopx console MiniApp on the new main and 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)

  1. 内置 miniapp: assets committed under product-domains/src/miniapp/builtin/assets/bitfun-loopx/; registered in BUILTIN_APPS (builtin.rs) with the content-hash seed policy of the 1.0.0 codebase; private_bridge_extension exposes app.loopx.* only to the verified built-in identity (builtin-bitfun-loopx).
  2. Rust contracts: product-domains/src/miniapp/loopx/{types,ports,policy,bridge}.rs — typed DTOs, narrow ports, pure lifecycle policy (cargo test -p openbitfun-product-domains --features miniapp covered by loopx_contracts).
  3. Services: services-integrations/src/miniapp/loopx_{cli,github,workspace}.rs behind the new miniapp-loopx feature — pinned sidecar selection + typed CLI argv/JSON translation + durable readback, GitHub intake adapter, Git/worktree service.
  4. Assembly/core: 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.
  5. Desktop: miniapp_loopx_api.rs (7 Tauri commands, registered in lib.rs, declared in the Product Operation Registry with Unsupported/ControllerLocal stances), bundled loopx sidecar resolution (BITFUN_RESOURCE_DIR), worker_host.js policy/environment handling.
  6. Packaging: scripts/build-loopx.mjs (pinned loopx v0.5.1 @ 1bb42f4c, PyInstaller onefile) invoked by scripts/desktop-tauri-build.mjs (prepareBundledLoopx + injectLoopxResource, --no-bundle skip) and scripts/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).
  7. Web UI: LoopxAPI.ts, loopxBridgeProtocol.ts, MiniApp scene/bridge updates, FlowChat/event wiring, locales (zh-CN/zh-TW/en-US).
  8. Docs: asset AGENTS.md/README.md updated to OpenBitFun naming; desktop AGENTS.md reconciled with the registry-driven peer/workspace policy.

Deviations / notes

  • BitFun-Installer lockfile 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).
  • Peer/workspace stances now live in remote_surface/table.rs (Product Operation Registry) instead of the removed hand-written tables; loopx commands are Unsupported on remote workspaces, ControllerLocal under Peer Device Mode (contracts to be negotiated across peers), CLI peer host refuses before dispatch.
  • LoopX CLI pin: v0.5.1 @ 1bb42f4c (same as feat(miniapp): add builtin bitfun-loopx console MiniApp #2382's later state).
  • Verification on the new codebase: 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)

  • Branch rebased onto latest origin/main (1a99a2d88), then updated by 233a6244d for UI consistency.
  • PR head: 233a6244d9515178e5737b20492fac9e94196e63.
  • No business/agent logic or architecture changes: this pass only updates MiniApp presentation, locale wiring for accessible names, and frontend color-registry metadata.
  • The previously uncommitted local LoopX UI changes are now included in 233a6244d (environment panel compaction, empty-state centering, connected-label hiding, Node runtime field/test update).

UI consistency review and fixes

Fixed:

  • Registered miniapp-bitfun-loopx in scripts/frontend-color-surface-registry.json;
    ode scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx` now passes.
  • Removed host-token fallbacks and raw fallback colors from style.css; all owner-facing colors now resolve through --openbitfun-* tokens/local aliases.
  • Raised owner-facing caption sizes to >=11px and interactive hit targets to >=32px.
  • Moved remaining hard-coded Chinese ARIA names to runtime i18n (data-i18n-aria-label) for the intake form, task rail, task list, and issue-detail landmark.
  • Included the pre-existing uncommitted UI polish changes.
  • Added the initial is-empty class to the task-rail markup so the empty placeholder is centered on first paint, before the first snapshot calls renderTaskRail.
  • Fixed the .task-rail.is-empty grid: one explicit full-width column, with .task-empty placed 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.mjs still reports 2 pre-existing failures:
    1. intake flow records an extra modelId: primary submission before auto;
    2. task rail order is queued before running.
  • thin-client-source-contract.test.mjs flags the --registry literal in the log-sanitizer replacement in ui.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 marketStrict runtime 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: unused std::os::windows::process::CommandExt in loopx_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 window OpenBitFun created, frontend page loaded).
  • After the final task-rail centering fixes, incremental desktop:dev rebuilt in 1m47s / 2m28s and started successfully again.
  • LoopX thin-client tests: 5/8 pass; the 3 failures above reproduce on the pre-rebase head, so they are not introduced by this update.

GitHub packaging Python availability:

  • .github/workflows/desktop-package.yml and .github/workflows/nightly-artifacts.yml both run actions/setup-python@v5 with python-version: '3.13' before pnpm install / desktop packaging.
  • scripts/build-loopx.mjs then creates a venv and installs pyinstaller + tzdata; scripts/desktop-tauri-build.mjs calls 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; the list attribute 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 checking forever; 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:

  • LoopxEnvironmentRemediationAction gained InstallNode / InstallGit, and
    the MiniApp bridge gained install_node_runtime / install_git_runtime.
  • New LoopxRuntimeInstaller downloads pinned, SHA-256 verified archives into
    the BitFun-managed runtime root <config>/openbitfun/runtimes/<component>/current:
    • Node.js v24.21.0 for Windows/macOS/Linux (x64 + arm64), verified against the
      official SHASUMS256.txt digests.
    • MinGit 2.55.0.5 for Windows (x64 + arm64).
  • ManagedRuntimeResolver understands the MinGit layout, and the process PATH is
    extended 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.
  • A missing Node.js runtime or Git binary now carries the matching remediation
    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-existing CommandExt warning).
  • cargo build -p openbitfun-desktop - passed in 18m53s and the fresh-test app launched, after pinning CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER to the MSVC link.exe. On this machine the default rust-lld link fails for zstd-sys/libgit2-sys with undefined wcscat/qsort_s symbols; 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_contracts keeps 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 does
    thin-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_runtime return a typed error) and the
environment 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.js
artifact 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 InstallNode action, and the fact always settles (never stuck in
checking).

Environment panel UX (2026-09-17)

  • Install actions render inline in each requirement row (LoopX engine / Node.js
    / Git) with the primary "Install compatible version" button style; the
    standalone LoopX repair card is gone.
  • The long English host detail is no longer printed under every requirement —
    the short version line stays and the full detail moves into the row tooltip,
    so the cards stay compact.
  • Layout follow-up: equal-width inline buttons, label/status/button packed to
    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.
  • "Check environment again" now disables while it runs and reports
    "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 a
    dependency 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.

  • The button no longer stretches across the row, so the three rows stay aligned
    without the oversized bar, and the right edge is a tidy column.
  • available renders the same pill with a check icon and the availability
    label, instead of a plain text status.
  • unavailable installs on click: the pending flag is set and the panel is
    re-rendered synchronously, so the pill flips to a spinner state immediately.
    Hover lifts it 1px and the press scales it down for immediate feedback.
  • The button carries data-state (install / installing / available /
    status) and each state is styled; the spinner reuses the existing lx-spin
    keyframes; 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 app
    relaunched and running (PID 20332).
  • thin-client-runtime.test.mjs still reports the same 2 pre-existing failures
    (extra modelId: primary submit, task-rail ordering); no new failures.

Install action names the pinned LoopX version (2026-09-17)

  • The LoopX requirement button now reads "安装 LoopX 1.0.1" / "Install LoopX
    1.0.1" instead of the vague "安装兼容版本" / "Install compatible version".
  • The MiniApp resolves that version from the sidecar fact detail
    (expected loopx <version>, falling back to the fact version) once and
    reuses it for the "downloading LoopX " notice, so the button and the
    notice agree.
  • The controller now keeps the expected loopx <version> marker in the sidecar
    fact 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):

  • Released desktop builds ship LoopX as a bundled PyInstaller binary
    (scripts/build-loopx.mjs -> resources/loopx/, injected by
    scripts/desktop-tauri-build.mjs, surfaced as LoopxCliSource::Bundled), so
    end users need neither Python nor network.
  • The in-product install action is the fallback for hosts without that bundle:
    it clones the pinned v1.0.1 source from GitHub and runs it with a managed
    Python 3.11+ interpreter (LoopxCliSource::PythonFallback).
  • A plain cargo build dev/fresh-test binary has no resources/loopx/ (that
    directory 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 app
    relaunched 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:

  • the MiniApp kept a single environmentInstallPending / environmentInstallRuntime
    pair plus one shared environmentInstallRequestId;
  • the controller serialised every repair behind a single install_in_progress
    flag;
  • because the request id was shared, the second and third clicks came back as
    duplicate ("request was already applied"), and the UI then painted the stale
    environment fact as that row's install failure (the Git row showed the
    workspace probe error as its own failure).

Fixed:

  • ui.js tracks installs per runtime in a Map (requestId / inFlight /
    snapshotCheckedAt). Each row observes its own environment fact and reports
    only its own outcome; the snapshot checkedAt ignores stale pushes, so a
    finished install can never repaint another row.
  • controller.rs replaces install_in_progress: AtomicBool with one slot per
    component (LoopX / Node.js / Git) via RuntimeInstallSlots.
  • new unit test 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, so
that card was taller than the LoopX / Node.js / Git rows.

  • environmentFact appends the short version to the row title, right after the
    label, instead of appending a separate line;
  • .environment-fact__version is an inline muted mono suffix again, so every
    requirement card keeps the same height.

Verification for this increment:

  • node --check ui.js - clean; thin-client-source-contract.test.mjs 6/6.
  • node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx - passed.
  • cargo build -p openbitfun-desktop - passed (6m57s), fresh-test app
    relaunched and running (PID 8228).

LoopX source install prerequisites are reported honestly (2026-09-18)

Installing LoopX from source on a machine without Python surfaced:

invalid packaged LoopX manifest: Python 3.11 or newer is required to
install LoopX from source

Nothing in that path touches a packaged manifest; the panel just printed the
literal adapter error.

  • adds LoopxCliAdapterError::LocalPrerequisites (displayed verbatim, 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.

Verification for this increment:

  • cargo build -p openbitfun-desktop - passed (6m00s), fresh-test app
    relaunched 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_command expects
    host-authored lane vision, while the runner instruction seeds
    host-authored issue-fix lane vision - unrelated to this change.
  • Environment after the run: 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 checking while it
works, and checking_runtime_environment_fact builds that fact from Default,
so it carries no remediation action and the row fell back to the generic status
pill.

  • environmentFactAction now 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
    lx-spin spinner until the environment fact settles.

Where the app-managed runtimes live

  • Root: path_manager.managed_runtimes_dir() = <config>/openbitfun/runtimes.
  • Installer: LoopxRuntimeInstaller stages under
    <root>/<component>/.staging-* and activates <root>/<component>/current.
  • Resolution: ManagedRuntimeResolver::managed_path_entries() returns the
    existing <root>/<component>/current[/bin|/cmd|/mingw64/bin] directories, and
    prepend_managed_runtime_path prepends 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 worktrees
    all use the managed binaries.
  • Verified on the fresh-test build, whose launcher strips the system PATH: the
    managed node.exe reports v24.21.0 and git.exe reports
    git version 2.55.0.windows.5, matching the environment facts exactly.

Timeline turns: pinned headers, expanded by default (2026-09-18)

  • .timeline-turn is now position: sticky; top: 0 with 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.
  • isTurnCollapsed defaults to expanded for every turn, completed tasks
    included: the stream is read from the bottom, where the conclusion lands, and
    followLogs already scrolls there on task selection. Only an explicit toggle
    collapses a turn (state.turnCollapseOverrides still remembers it).
  • Removed the now-unused defaultRawLogsCollapsed helper and the total
    argument plumbing that only existed to auto-collapse history.

Verification for this increment:

  • node --check ui.js - clean; thin-client-source-contract.test.mjs 6/6.
  • node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx - passed.
  • cargo build -p openbitfun-desktop - passed (4m09s), fresh-test app
    relaunched 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=Resume request for two presses, so one
press 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.

  • 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 behind its
    repository keeps the same rendered state, so an immediate clear read as "the
    click did nothing".
  • .is-pending owner action buttons render a small spinning ring.

Verification for this increment:

  • node --check ui.js - clean; thin-client-source-contract.test.mjs 6/6.
  • node scripts/audit-frontend-colors.mjs --surface miniapp-bitfun-loopx - passed.
  • cargo build -p openbitfun-desktop - passed (2m58s), fresh-test app relaunched
    and running (PID 25136).

@xielixing

Copy link
Copy Markdown
Author

Replaces closed PR #2382 (feat(miniapp): add builtin bitfun-loopx console MiniApp) — same work reapplied on the current \main, per the migration note on #2382. See #2382 for the original review context.

@xielixing xielixing changed the title feat(miniapp): add built-in bitfun-loopx console MiniApp (replaces #2382) [WIP]feat(miniapp): add built-in bitfun-loopx console MiniApp (replaces #2382) Sep 6, 2026
@GCWing

GCWing commented Sep 10, 2026

Copy link
Copy Markdown
Owner

beta旗舰暂不进行大特性合入,稳定性为主,后续阶段再评估

@xielixing
xielixing force-pushed the builtin/loopx-miniapp branch 2 times, most recently from be78844 to 79194cc Compare September 15, 2026 07:10
xielixing and others added 24 commits September 17, 2026 14:24
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.
xlx1212 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
xielixing force-pushed the builtin/loopx-miniapp branch from 3ca2a1a to f203d72 Compare September 17, 2026 06:29
xlx1212 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.
@xielixing xielixing changed the title [WIP]feat(miniapp): add built-in bitfun-loopx console MiniApp (replaces #2382) feat(miniapp): add built-in bitfun-loopx console MiniApp (replaces #2382) Sep 18, 2026
@xielixing

Copy link
Copy Markdown
Author
img_v3_0215l_738ab16e-6a7e-4d54-81ca-f9ca3eeb0deg 符合ui一致性

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`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants