feat(miniapp): add builtin bitfun-loopx console MiniApp - #2382
Closed
xielixing wants to merge 3510 commits into
Closed
feat(miniapp): add builtin bitfun-loopx console MiniApp#2382xielixing wants to merge 3510 commits into
xielixing wants to merge 3510 commits into
Conversation
* fix(chat): make remote file mentions reliable * fix(chat): tolerate sessions without config metadata * fix(chat): register mention picker error state
* refactor(peer): add the device surface identity and epoch contract Foundation for the multi-device rework: a DeviceSurfaceId that names which device a piece of state belongs to, a monotonic activation epoch with an AbortSignal, a typed SurfaceChangedError for unwinding stale work, and the key-scoping helper every per-device cache must use. Nothing consumes it yet; the layers land on top of this contract. * refactor(peer): isolate device surface state and switching
fix(input): simplify file mention path display
Switching devices repeatedly left a session showing no live output and no
recovery: the stream stopped and nothing brought it back.
The agentic subscription is this window's only live view of a running Turn,
but its lifetime was tied to workspace bootstrap. A switch tore it down, and it
was rebuilt only as a side effect of FlowChatManager.initialize() — which a
newer switch is allowed to supersede with SurfaceChangedError, and which the
workspace effect does not re-run when both devices share a workspace path.
Nothing retried.
The attach loop was then gated on that same subscription:
if (!agenticEventListener.getIsListening()) return;
so the one path designed to repair a missing stream refused to run precisely
when the stream was missing. Live events gone, snapshot repair disabled, frozen
until restart. AppLayout also reported the superseded bootstrap as a product
failure, hiding the cause behind a generic toast.
Make the two halves independently recoverable:
- FlowChatManager re-arms on every surface activation and retries a failed
start on a backstop timer, so the subscription no longer depends on a
bootstrap that may legitimately be abandoned.
- The attach loop treats a dead subscription as a reason to reconcile *and*
re-arm it. The runtime cursor fence already covers the snapshot/live race
while it comes back up.
- A superseded bootstrap is control flow, not a failure: no toast, and it
ensures the subscription instead of leaving the window dark.
The Runtime already materializes an exact, cursor-ordered projection of the executing Turn — but only in memory. Everything durable lags it: client persistence is debounced into coalescing windows, and the persisted Session view deliberately stores an executing Turn as idle so a restart cannot revive work. So the one complete record of work in flight dies with the process, and a client returning to that Session is served a Turn frozen at the last checkpoint. That is the gap every attach guard has been working around. Give the journal an optional durable store: a port here, filesystem policy in the host that owns Session storage. A materialized event persists the projection, a terminal Turn discards it because the persisted Session view owns the record from then on, and `snapshot` falls back to the stored projection when memory has none. A stored projection keeps its original stream_id, so a client correctly reads it as a different Runtime process and replays it whole rather than comparing cursors across processes. No behaviour changes without a store attached; hosts are wired separately.
Makes the in-flight Turn record real-time and atomic instead of reconstructed from a periodic summary. The port takes one event at the moment it enters the ordered delivery stream, not a snapshot on a timer, and the store appends it to a per-Session JSONL log through an already-open handle. Reload replays that log through the Runtime's own materialization, so a restored projection and a live one come from a single implementation rather than two that can drift. No fsync per event: an append reaches the page cache immediately, which already survives the process crash this log exists for, and a disk flush per token would throttle the stream it records. Scope is deliberately the Turn in flight. The persisted Session record stays the history of completed work — it cannot represent a running Turn because it stores one as idle so a restart never revives work — and a terminal Turn drops its log, so the two never describe the same thing at once. Handles the failure modes a real log has: a torn final line after a hard kill keeps everything already durable, and a log left by an older Runtime process is superseded rather than replayed as current progress.
…evice Wires the durable event log into both Hosts and removes the other way a device could stop showing progress after repeated switching. Desktop and CLI Peer Hosts now append the executing Turn's events to one shared log directory. They can own the same Session at different times, so a Turn left running by one must be replayable by the other; a Host that restarts mid-Turn now serves the real state instead of the last client checkpoint, which the persisted Session record stores as idle. Separately: presence loss marks an attachment `lost` immediately, `lost` is terminal, and `connect` refuses a lost entry. One presence blip during a burst of switching therefore stranded a perfectly reachable device for the rest of the session — switching to it kept failing and its sessions stayed frozen. Presence returning now clears that dead entry so the next switch attaches a fresh one. Keepalive-lost is deliberately untouched: presence proves reachability, a missed ping does not.
Keep the model-visible schema strict while tolerating common malformed CallDeferredTool payloads at runtime. - treat missing args as an empty object - merge top-level tool fields into args with nested values taking priority - replay canonical tool_name and args payloads to the model - project normalized inputs in deferred tool cards - cover parsing, execution, replay, and UI behavior
Treat GetToolSpec as the deferred tool admission boundary instead of requiring every invocation to pass through CallDeferredTool. - Allow direct tool calls after loading a current catalog spec - Preserve missing and stale spec rejection - Keep allowlist and runtime restriction checks unchanged - Add coverage for direct-call acceptance and pre-spec rejection
- List direct tool names alongside deferred tools in the tool-calling guide. - Render GetToolSpec assistant results with a unified calling schema. - Fix the deferred tool name with const and nest the original schema under args. - Preserve the structured input_schema returned in tool result data. - Update prompt and tool contract tests.
Remove the expandable description and input schema view from GetToolSpecCard. Keep the card non-interactive, trim its appearance contract, and update coverage for compact-only rendering.
fix(desktop): disable update checks in development
Treat tent hover and one-axis half-fold separately, keep wide content outside system Navigation, and span detail across both remaining tri-fold screens instead of a single hinge-free band. Co-authored-by: Cursor <cursoragent@cursor.com>
…de-layout feat(harmonyos): keep tri-fold conversation on remaining screens
A surface switch is a view change, not a change of execution. Locally created sessions stay at historyState new, so attach must still run, and a turn already submitted to the host must not be re-queued or drained while the projection is being repaired.
- Add a provider-neutral request context with a stable cache route key - Send prompt_cache_key consistently across tool, retry, and finalize rounds - Parse cache write token usage without conflating missing and zero values - Add content-free request and response cache diagnostics
Capture and persist the authoritative Responses output layout, including opaque reasoning state, for subsequent agent rounds. - Replay reasoning, messages, and function calls in original order - Bind replay state to the resolved model runtime fingerprint - Fall back atomically for incompatible fingerprints or layouts - Preserve legacy deserialization and non-Responses behavior - Cover capture, persistence, ordering, and fallback paths This keeps multi-round prompts prefix-stable so provider-side prompt caching can reuse prior reasoning and tool history.
Persist a stable prompt cache lineage independently from the concrete session ID and inherit it when sessions retain a shared prompt prefix. - Route provider caches by lineage instead of model or prompt scope - Keep fresh subagents on independent cache identities - Reuse lineage for forked, BTW, and branched sessions - Pass request context through compaction summary requests - Keep binding fingerprints separate for encrypted reasoning replay
Route BTW turns through the Desktop UI submission policy so the runtime prompt retains the Chat Image Display context. Pass the policy from the desktop adapter into the coordinator and add regression coverage for the BTW output surface classification.
Remove model-binding fingerprints from persisted response replay state and request context. Forward valid encrypted reasoning items without local model filtering, allowing the Responses service to determine compatibility while retaining atomic fallback for malformed replay layouts.
The sidebar asked the user to pick a conversation *source* — Local or Remote — before showing them anything. That is an implementation detail: where a session runs is not how anyone looks for it, and the switcher cost a permanent row of chrome to express it. Conversations and workspaces now share one scroll. They are different kinds of thing (a timeline and a set of places), so they stack rather than compete for the same pane, and neither carries a label saying which machine it runs on. The workspace section arrives through a `@BuilderParam` slot: the sidebar has no business knowing about remote state, so the host wires it. - Drop `ConversationSource` and `ConversationSourceSwitcher`. `AppRouteContract.conversationSource`/`routeForConversationSource` become `isRemoteRoute`/`remoteSurfaceDestination`, with `isRemoteRoute` stated as the complement of `isGeneralComposerRoute` so the two cannot drift apart. `remoteSurfaceDestination` resumes an in-flight remote session instead of dropping the user on a picker they did not ask for. - Cap the conversation list at six rows behind an overflow row, so the workspace section stays visible on a phone without hunting for it. The shared scroll means the list can no longer take every pixel it wants. - Fade rows out under the floating footer via a new `page_bg_fade` token (PAGE_BG at zero alpha, defined per theme). Without it a workspace row mid-scroll is visually sliced by the chat button sitting on top of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The workspace section's "connect a desktop" row was wired to `onSidebar.enterCode`, which only moves the visible surface to Remote. Offline that resolves to `AppRoute.RemoteHome`, whose wide-layout detail pane is `RemoteSurfaceHost` in placeholder mode: a header naming the desktop the phone last held and two lines of status text, with no control on it. So the row named the last-known machine on the right and then did nothing, and pressing it again returned early on the `isRemoteRoute` guard. `RemoteSurfaceEntryPolicy` already states that reaching the remote surface and asking to pair are different commands and must not share an entry point. The row was on the wrong side of that line: give it `onSidebar.connectDesktop`, which opens the connect sheet. The section header's "+" shares the same event. Workspaces come from the desktop and the phone cannot create one, so it is relabelled by the connection it adds rather than a workspace it cannot make. Also drop workspace entries with no path. `syncRemotePageSummary` seeds the current workspace with the placeholder name 「未连接」 and an empty path, and the projection turns any named current workspace into an entry. On the Remote surface that never showed, because you only got there connected; in the sidebar it is on screen from launch, so a reconnecting phone with no workspace data yet listed a folder called 「未连接」 that opens nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The back arrow on the desktop picker and on the account page sat visibly left of the circle it lives in. Measured on device: circle centre x=422.5, chevron ink centre x=399.5 — about 7vp adrift. The cause is SymbolGlyph sizing, not the surrounding layout. A chevron's natural advance box is roughly half as wide as it is tall (~11.6vp at fontSize 23, against 23.2vp of height). Forcing .width(26).height(26) does not re-centre the glyph in the enlarged box; it draws left-anchored, so the ink lands 26/2 - 11.6/2 ≈ 7vp left of where the box centre says it should. Vertically it looked fine only because the natural height already matched the forced one. Probing the two glyphs side by side with tinted backgrounds confirmed it: the unsized chevron's box measured 37px wide and centred in the circle, while a magnifier forced to the same 26vp filled its box and stayed centred, because its ink is square. Dropping the forced size lets the Stack/Button centre the natural box. Re-measured after the change: ink centre x=422.5 against a circle centre of 422.5. Trailing chevrons elsewhere keep their forced width — there it acts as a row gutter rather than as a centring box, and removing it would move the rows rather than fix them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Git (since 2.35.2) and libgit2 refuse to operate on a repository whose directory owner differs from the current user unless its root is listed in safe.directory. BitFun surfaced that guard as an opaque command failure, so read-only flows such as Review looked indistinguishable from "not a repository" and blocked operations had no recovery path. Repository ownership trust now lives in one platform-neutral module: owner rejections are classified into a typed Git error, trust state is reported without side effects, and safe.directory is written only after explicit user confirmation, followed by a check that Git accepts the repository. The confirmation flow works on local, remote, and peer surfaces: trust is applied on the machine the user is on, while controllers receive the diagnosis and the manual git command. The Git scene gains an authorization entry, Deep Review target reads are serialized around a single trust decision, and prompt dedupe keys are normalized per platform.
The crooked back chevron fixed in b48f60d was not a one-off. Probing on device showed SymbolGlyph draws its ink left-anchored inside any box the caller forces wider than the glyph's natural advance box (vertically it stays centred, so only the horizontal axis is affected). Most symbols have a square advance box of fontSize x 1.013, but chevron_left and chevron_right are only half as wide as they are tall, so every `.width(N).height(N)` on a chevron shifted it left of where the surrounding container promised to put it. Two shapes of the same bug: - Glyphs inside a fixed-size centring Stack or circular Button: the forced box overrode the container's centring and pulled the icon off centre by up to 4.5vp. Dropping the size lets the container centre the natural box. Measured on the settings close button: ink centre now 0.3vp from the circle centre, was ~7vp. - Trailing disclosure chevrons in list rows: the forced 16-18vp box left ~10vp of dead space between the arrow and the row's right padding, so the arrows never lined up with the card edge. The right gap on the model row now measures 18.6vp against the 18vp padding. Collapse indicators that toggle between chevron_right and chevron_down keep their fixed slot -- it is what stops the label jumping between the two states -- but the slot moves to a wrapping Stack and the glyph inside it goes unsized, matching the idiom already used in SubagentTaskCard. Verified on device that the label x is identical in both states. Leading icon slots in list rows are left as they are: because ink is left-anchored, the forced width is what keeps icon and text left edges aligned across rows of differing fontSize. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the self-contained bundled JS worker with a typed LoopX host so task intake, workspace preparation, CLI supervision, and event delivery run through reviewed product-domain contracts instead of an embedded script runtime. - Add LoopX product-domain contracts (policy, ports, types) under bitfun-product-domains. - Add the LoopX controller assembly (store, agent adapter, event subscriber) in bitfun-core. - Add concrete LoopX services (CLI process runner, GitHub intake, isolated workspace) in bitfun-services-integrations. - Add the desktop miniapp_loopx_api Tauri surface and peer-host policy entries. - Shrink the builtin bitfun-loopx web asset to a thin client that drives the host through the bridge (loopxBridgeProtocol, MiniAppAPI, useMiniAppBridge). - Add contract tests for the LoopX ports, bridge, and thin-client source shape.
authorize_builtin compared app.runtime.content_hash (a hash of the MiniApp domain fields) against builtin_content_hash (a hash of the bundled asset files). The two hashes cover different inputs with different framing, so they can never match and the LoopX controller was always rejected as modified content. Replace the comparison with builtin_source_matches, which compares the installed source files (html/css/ui_js/worker_js/esm_dependencies/npm_dependencies) verbatim against the bundled built-in bundle, excluding the meta.json identity/timestamp rewrite performed at seed time.
…th probe The native-host migration dropped two behaviors the previous worker runtime provided. - Restore the model dropdown: the thin client now populates the model <select> from the host model catalog through app.ai.getModels() and threads the chosen model id through resolveIntake/createTask. - Add a pre-flight GitHub auth probe: refresh_environment now calls the new LoopxCliPort::probe_github_auth (a /rate_limit check) and populates the github_auth environment fact before intake, so an auth or rate-limit failure is visible up front instead of surfacing as a 403 at send time. - Distinguish GitHub 403 rate-limit vs forbidden/private in the intake error message.
The restored dropdown called app.ai.getModels(), which is gated on the MiniApp AI permission and failed for the built-in LoopX app (which drives the native controller, not the raw AI bridge), leaving only the auto option. Add a dedicated loopx.listModels bridge method returning the host-configured chat models (same set as the main session picker), gated on the verified-builtin check instead of the AI permission, and register it in the peer/remote policy lists.
…-pane console - thread host-resolved issue state and labels into the LoopX workflow plan instead of fabricating open-state metadata - persist the source-backed candidate admission receipt at goal creation via workflow-plan --fetch-candidate-evidence --goal-id (best-effort, issue-kind items only) and persist the newer packet over the unconfigured plan packet - teach the agent CLI contract the real issue-fix subcommands, the pr-lifecycle execute-transition form, and continuous_monitor cadence flags - derive the goal objective from the resolved issue title - add uuid to the miniapp-loopx feature and register its boundary ownership - rework the bitfun-loopx console into task rail / task detail / timeline columns with a sticky owner-decision card pinned in the detail column
xielixing
force-pushed
the
builtin/bitfun-loopx-miniapp
branch
from
September 2, 2026 10:11
c45b4b2 to
b35f2d5
Compare
…request collapsed - dispatch known issue-fix gate kinds (gated content read, maintainer clarification, authority grant, draft-ready) to human-oriented zh-CN headlines, explanations, and approve/reject effects with en-US parity - stop rendering the agent's raw English todo text as the decision body; it moves into a collapsed 'original request' details block - strip the [P0]-style priority prefix and adapt authority scope labels to the active locale
…nize issue view - schedule exactly one corrective turn after a NoDurableProgress settlement, appending a host note that routes the agent back through the CLI write boundary instead of parking the task for manual recovery - auto-approve read-only user gates surfaced by the settlement path (same policy as the drive-turn inspector, host-attributed and durably logged) - project a decision card for waiting/recovery tasks from the durable summary - remap recovery states to pending-approval/pending-recovery labels (amber, not error red) and drop the stale five-stage pipeline projection - expand the Issue description panel by default and move it above the latest progress summary as the single status source - hide routine engine heartbeat events and collapse thinking blocks in the merged timeline; remove the pause and resume-auto-follow controls
- carry tool input params on Completed/Failed/Cancelled tool events so late projections can render what ExecCommand ran and which file Read opened - render only agent output blocks in the merged timeline; scheduler and engine heartbeat milestones are dropped (decision card + summary cover them) - merge consecutive tool lifecycle events into one block whose text tracks the latest summary, and fold tiny text fragments into the previous block - remember expanded thinking blocks across streaming re-renders and filter empty/marker thinking chunks - default the issue view to the first task in execution order when nothing is selected
schedule_next_for_repository now also considers Preparing tasks: a task reserved for workspace preparation whose drive never completed would keep the whole serial repository line idle after the running slot freed up (the scheduler previously only picked Queued tasks). Re-driving a Preparing task is safe - reserve_repository bounces it back to Queued when the slot is still taken. Parking settlements that fail to advance the queue now log the remaining non-terminal task states so stalls are visible.
Thinking and text stream in as per-chunk events; the previous retention cap (2400 events / 1.2M chars) evicted everything older than the last few rounds within minutes, leaving the merged timeline with a handful of rows and an unscrollable blank area. Raise the caps (50k events / 8M chars) and the per-turn dedupe window (800 to 4000) to match chunk granularity.
Batch creation used to enqueue every task at once, letting concurrent drive_task calls race for the repository slot; the winner could be the last-created task, which both broke deterministic execution order and made the default-focused task differ from the one about to run. Enqueue only the first created task per repository and chain the rest through schedule_next_for_repository after each settlement (Preparing tasks are schedulable since the previous change). Also document the desktop target-GC vs manual-build race in the desktop AGENTS guides.
Clicking the repository resume button could silently do nothing when the resume target went stale between render and confirm. Both silent paths now show an error notice so the failure point is visible.
The durable tool-activity events already carry a redacted input summary (command, file path, pattern) in their details since the params projection change, but the merged timeline's fallback rows rendered only the generic 'Tool completed: ExecCommand' label. Append the summary to the row message.
…ecution order - the decision card now differentiates recovery from approval: interrupted work shows a primary 'resume retry' button (per-task resume), gate waits point at the approval panel - the task rail and default focus follow actual execution order: running, preparing, queued (creation order), retry/waiting, recovery, then finished work most-recent-first - instead of scattering tasks by state priority with a most-recently-updated tie-break
- loopx_summary_v1 report contract: agent final responses end with a validated fenced JSON block (verdict / reproduction / segment kind / decision / blockers / next step with conditional evidence); the controller parses and persists it, the UI renders verdict badges and human-readable sections, falling back to raw text for legacy turns - recovery_reason on task snapshots (host_restart, execution_failure, settlement_unverified, repository_paused, manual_restore), surfaced as a human explanation on the recovery card - monitor-class todos (*_monitor, issue_fix_track_*) stop driving back-to-back turns: pinned v0.5.1 emits no numeric monitor_wait hint, so the host parks re-checks on a 15-minute compatibility cadence anchored to the last durable settlement and yields the repository slot to queued sibling issues; the depth-first sticky lane yields for monitor successors while real work segments still continue deep - repository resume excludes tasks the agent already concluded as resolved upstream (mirrors the UI heuristic) - agent re-entry instruction pins the exact quota spend-slot argv with the scheduler execution context declaration and appends the summary contract - issue view: reproduction badge renders only when it carries signal (not_applicable hidden), technical facts folded into a collapsed muted block, dead five-stage pipeline code removed - durable compensation allowance re-arms after a clean settlement
Runtime findings from the 2026-09-05 five-issue run (0/5 completed, all parked in recovery) drive four behavior corrections: - drive one autonomous replan turn when the plan runs dry with an open replan obligation: the pinned CLI v0.5.1 projects exactly that frontier and the quota guard admits it; parking there stranded every task before any goal could close. A todo-less RunNow frontier without an obligation still parks, now with an explicit plan_exhausted reason and a guided recovery card - salvage the typed ok:false payload from turn-plan process exits when the replan frontier fails the host-bound route lineage check, so settlement and goal inspection degrade to the equivalent read-only projection instead of a raw process error - carry the replan obligation through the goal snapshot and give replan turns an explicit re-entry clause requiring a typed --repair-delta-kind ACK (a delta-less ACK is stored as a no-op) - recover completed-turn RetryRequired settlements (validated durable writeback, missing quota receipt) from the authoritative goal projection with a loud recorded event instead of a manual resume; cancelled and interrupted turns keep the explicit recovery path, and the host never retries or fabricates the receipt Also corrects the MiniApp contract doc from the runtime data and syncs the thin-client tests with the current UI contract (decision card, execution-order rail, explicit publish grant, merged timeline), narrowing the heartbeat lint to actual scheduling calls.
Owner
|
Thank you for your contribution. The If your changes are still needed, please reapply them on a fresh branch based on the new We apologize for the disruption and appreciate your understanding. |
3 tasks
Author
|
Replacement PR: #2836 (port of this work onto the 1.0.0 \main). |
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
新增内置 MiniApp bitfun-loopx(品牌统一为 bitfun-loopx,
assets/bitfun-loopx/即权威源码,不依赖任何外部仓库快照):粘贴 GitHub Issue 链接,由 BitFun 宿主 Agent 驱动本机 loopx 持续修复,心跳调度、人工审批、中途插话。本分支共 6 个提交(初始内置版 + sidecar 打包 + 后续打磨):
builtin/assets/bitfun-loopx/五件套 + meta + README;BUILTIN_APPS注册builtin-bitfun-loopx;原名 LoopX 控制台已统一品牌为 bitfun-loopx(标题、会话名、PR 标记、User-Agent);数据目录前向改名(新装~/.bitfun/bitfun-loopx/,旧loopx-console继续沿用,不搬动用户数据);探测自愈(worker 无条件把本应用 vendor 目录纳入候选)。scripts/build-loopx.mjs构建期拉取 pinv0.2.13源码 → venv + PyInstaller 编译单文件二进制 → 输出 LICENSE / TRADEMARKS.md / manifest.json(版本、commit、sha256、构建工具链)到src/apps/desktop/resources/loopx/(gitignore,二进制不进仓库,每次打包构建重新拉取+编译);desktop-tauri-build.mjs在 bundle 构建时动态注入 sidecar 资源(dev--no-bundle跳过);desktop 解析资源目录 → 经 worker pool 以BITFUN_RESOURCE_DIR注入 JS worker → 内置二进制最高优先级(用户机器零 Python/git/网络),vendor/pip 兜底;CI 增加setup-python@v5(3.13)。.topbar/.brand相关 CSS 一并清理)。Fixes #
Type and Areas
Type: Feature
Areas: Rust core(product-domains 内置 MiniApp 资产)、MiniApp runtime、Desktop(资源解析/打包)、CI、Docs、Installer
Motivation / Impact
loopx(huangruiteng/loopx,MIT)是给长任务 Agent 记账的控制面 CLI:目标、待办、门禁、额度都记,但自己没有调度器和执行器。bitfun-loopx 补上这两样:心跳当闹钟,BitFun 宿主 Agent 当手。市场版受
marketStrict运行时档限制跑不动修复循环,内置版不受此限。此前 loopx 依赖运行时拉取(用户机器需要 Python 3.11+、git、网络)且探测失败率高(本机实测「未检测到 loopx CLI」)。现在构建期编译 + 安装包 sidecar 内置,打开即用;MIT 再分发义务随包履行(LICENSE/TRADEMARKS/manifest 随包 + THIRD_PARTY_NOTICES 收录)。
Verification
cargo test -p bitfun-product-domains --features product-full builtin_miniapp→ 2 passed, 0 failed(id 顺序断言 + meta/deps 契约测试)pnpm run check:github-config→ 14/14;node --check通过 worker.js / build-loopx.mjs / desktop-tauri-build.mjspnpm run build:loopx产出loopx.exe(15.1 MiB,loopx v0.2.13 @ 7232dca…),--version输出loopx 0.2.13;应用启动日志确认seeded builtin miniapp 'builtin-bitfun-loopx'+Resolved bundled loopx CLI resource dir: …\resourcesshell.allow=[loopx, python, py, git]、net.allow仅api.github.com/github.com;GitHub 凭据只存本机应用存储(gh CLI 或粘贴的 PAT),不写入 git config;发布 PR 必须人工批准。Reviewer Notes
--no-bundle)与内置二进制缺失时走 vendor/pip 兜底。LOOPX_VENDOR_REF必须同步升级(当前均v0.2.13)。src/apps/desktop/resources/loopx/在.gitignore,不进仓库;macOS/Linux 的实际编译产物建议在 release/nightly 流水线观察一轮(Windows 已本机验证)。Checklist