Skip to content

Commit 193ee9c

Browse files
fix(webui): close Mini App review gaps
1 parent 494931d commit 193ee9c

5 files changed

Lines changed: 52 additions & 48 deletions

File tree

‎packages/local-runtime-v2/src/service/miniapp/composition.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export interface RuntimeMiniAppServiceOptions {
2929

3030
export function resolveRuntimeMiniAppOptions(
3131
input: RuntimeMiniAppServiceOptions & {
32-
readonly runtimeOwnerKind?: string;
3332
/** The runtime owner policy's `wiring.miniAppService` cell for this host. */
3433
readonly miniAppService?: boolean;
3534
},

‎packages/local-runtime-v2/test/unit/miniapp/runtime-miniapp-options.test.ts‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe('resolveRuntimeMiniAppOptions', () => {
1212
expect(resolveRuntimeMiniAppOptions({ miniAppService: false })).toBeUndefined();
1313
});
1414

15-
it('keeps explicit options reachable, including with no declared owner kind', () => {
15+
it('keeps explicit options reachable even when the policy cell is false', () => {
1616
const miniApp = { dataDir: '/tmp/miniapp' } as Parameters<
1717
typeof resolveRuntimeMiniAppOptions
1818
>[0]['miniApp'];
@@ -21,11 +21,4 @@ describe('resolveRuntimeMiniAppOptions', () => {
2121
expect(resolveRuntimeMiniAppOptions({ miniApp, miniAppService: false })).toBe(miniApp);
2222
});
2323

24-
it('no longer reads the owner kind directly, so a row cannot disagree with the cell', () => {
25-
// The cell is the only owner-derived input: an electron runtime whose row does not carry it
26-
// gets no service, which is what makes the capability flag and the service agree.
27-
expect(
28-
resolveRuntimeMiniAppOptions({ runtimeOwnerKind: 'electron', miniAppService: false }),
29-
).toBeUndefined();
30-
});
3124
});

‎packages/webui/docs/adr/0020-webui-miniapp-view-and-runtime-seams.md‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# WebUI Mini App view and its runtime seams
22

3-
**Status:** Product direction accepted and the decisions below settled; implementation is pending and
4-
the questions at the end of this record remain open.
3+
**Status:** Product direction accepted and implemented in the Mini App hosting stack.
54
**Date:** 2026-10-20, revised 2026-10-26.
65

76
The WebUI will host Mini Apps. A Mini App's page is rendered as an iframe the client loads
@@ -449,11 +448,18 @@ page's own scripts run, so the proxy injects a script tag for it before `</head>
449448
`Content-Length` because the body it sends is not the body it received. Everything else — paths, methods,
450449
status codes, the page's own headers — passes through unchanged.
451450

452-
## Open implementation questions
453-
454-
1. **The bridge's method-level error mapping.** Which failure code each bridge call answers when the
455-
host cannot serve it — an unsupported capability, a full queue, a runtime that stopped between two
456-
calls — and how a page's request identifier survives the `postMessage` boundary.
451+
## Bridge request and error behavior
452+
453+
The bridge preserves the page's `requestId` through the `postMessage` request, the host operation
454+
and the response or event, so concurrent calls settle only the promise that started them. The host
455+
validates the source frame and its origin before it accepts a request. Malformed payloads answer
456+
`invalid_request`; a duplicate in-flight request id answers `busy`; a page without a host session
457+
answers `not_permitted`; and a runtime without the Agent capability answers `runtime_unavailable`.
458+
For supported calls, the service's documented failure code is preserved. If transport fails after a
459+
turn or call may have taken effect, the bridge answers `result_unknown` rather than inviting an unsafe
460+
retry. The shared contract and runtime Agent contract define the accepted failure codes and the
461+
`busy` reasons (`packages/shared/src/miniapp-surface.ts` and
462+
`packages/local-runtime-v2/src/service/miniapp/agent/contracts.ts`).
457463

458464
## Research findings
459465

‎packages/webui/docs/known-gaps.md‎

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Capabilities the WebUI does not have, verified against the tree. Each entry says
44
how, and what remains unverified. "Not implemented" and "not decided" are different states and are
55
kept apart here; the undecided ones are also in `open-questions.md`.
66

7-
Last verified: 2026-10-26, against `webui` at `88c83655`. This list covers capabilities; open
7+
Last verified: 2026-10-27, against the Mini App hosting stack merged with `origin/webui` at
8+
`ba946c06`. This list covers capabilities; open
89
decisions are in [`open-questions.md`](open-questions.md).
910

1011
## 1. Connector and App tools depend on a beta feature, and on the model finding them
@@ -43,11 +44,12 @@ them. They are listed so a future reader does not rediscover them as wiring gaps
4344

4445
| Site | Gate | What the WebUI does not get |
4546
| --- | --- | --- |
46-
| `local-runtime-v2/src/service/miniapp/composition.ts` | `runtimeOwnerKind !== 'electron'` → `undefined` | The miniapp service |
4747
| `local-runtime-v2/src/runtime.ts` (`greetingEnabled`) | `runtimeOwnerKind === 'electron'` | The startup greeting |
4848
| `local-runtime-v2/src/service/llm-context-inspector/initialize.ts` | `runtimeOwnerKind === 'tui'` plus `MAVIS_TUI_LLM_CONTEXT_INSPECTOR=1` | The context inspector (the terminal client can opt in; the WebUI cannot) |
4949

50-
The miniapp and inspector gates are service-availability decisions, not client-policy bugs.
50+
The inspector gate is a service-availability decision, not a client-policy bug. Mini App service
51+
availability is driven by the runtime owner's `wiring.miniAppService` cell, including the WebUI row;
52+
the service composition no longer compares `runtimeOwnerKind` directly.
5153

5254
## 3. The data directory is displayed, not relocated
5355

@@ -118,35 +120,7 @@ The bridge's **restricted action set is otherwise the same 28 actions** the isol
118120
executes, and both are executed by Browser Core — one implementation over two transports. See
119121
[`browser-use/10-browser-core-integration.md`](browser-use/10-browser-core-integration.md).
120122

121-
## 9. The Mini App capability is not reachable from this client
122-
123-
The runtime owns a Mini App service — a supervisor, a node runtime that spawns the Mini App's own
124-
process, a plugin package reader, and a session Mini App lifecycle. None of it is reachable from
125-
the WebUI:
126-
127-
- `local-runtime-v2/src/service/miniapp/composition.ts` returns no options unless the runtime owner
128-
kind is `electron`, so in the WebUI process the Mini App supervisor is never initialized and
129-
`miniappAvailable` is false.
130-
- `CliService` has no Mini App method, so there is no client entry for the capability the runtime
131-
already owns in `application/session/process-local-miniapp-application.ts`.
132-
- The client has no Mini App view, no proxy, and no page bridge.
133-
134-
The Mini App contract in this repository is also one generation behind the implementation shipped
135-
in the desktop client: `packages/shared/src/miniapp-surface.ts` here is 54 lines against 1267
136-
there, and this repository's runtime has no credential guard on the Mini App's own HTTP server.
137-
The newer contract is in neither this repository nor the public upstream tree, so it does not
138-
arrive by synchronization. The accepted direction for this client — which generation of the contract
139-
is taken, what the bridge carries, where the view lives and how a standalone presentation is
140-
tracked — is in [ADR 0020](adr/0020-webui-miniapp-view-and-runtime-seams.md).
141-
142-
The owner-kind gate is also decided in the wrong place. The runtime owner policy resolves once per
143-
host and drives the other owner-derived service gates — cron, channels, the scheduler — from its
144-
cells, while the Mini App gate compares the raw kind string inside its own service
145-
(`local-runtime-v2/src/service/miniapp/composition.ts`). That is why §2 lists it as a branch no
146-
change to this client's row can reach. The accepted shape moves the Mini App service into the
147-
policy as a wiring cell this client's row carries.
148-
149-
## 10. A symlink out of the workspace is not detectable by the diff boundary
123+
## 9. A symlink out of the workspace is not detectable by the diff boundary
150124

151125
**Implemented, with a stated limit.** A turn's diff can name a file outside the workspace, and the
152126
client now withholds that file's body: the entry keeps its name, its status and its line counts,

‎packages/webui/test/browser/miniapp-panel.spec.mjs‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,38 @@ test("the launcher's 应用 tile opens the picker and the chosen Mini App is fra
137137
await expect(page.getByTestId("workspace-panel")).toHaveAttribute("data-active-tab", "miniapp");
138138
});
139139

140+
test("a Mini App owned by another client reports the refusal and stays unopened here", async ({ page }) => {
141+
await configureFixture(page, () => {
142+
window.__WEBUI_FIXTURE_SETUP__ ??= [];
143+
window.__WEBUI_FIXTURE_SETUP__.push(() => {
144+
window.__fixture.setResponse("listMiniApps", { miniApps: [{
145+
pluginId: "demo.miniapp",
146+
displayName: "演示应用",
147+
runtime: { kind: "process", status: "running" },
148+
}] });
149+
window.__fixture.refuseOperation(
150+
"openMiniApp",
151+
"OWNED_ELSEWHERE",
152+
"Mini App is already running in Desktop",
153+
);
154+
});
155+
});
156+
await openApp(page);
157+
await openWorkspacePanel(page);
158+
await page.getByTestId("workspace-launcher-tile-miniapp").click();
159+
await page.getByTestId("miniapp-picker").getByRole("button", { name: "打开" }).click();
160+
161+
await expect(page.getByTestId("miniapp-picker")).toContainText(
162+
"已在其他客户端运行:Mini App is already running in Desktop",
163+
);
164+
await expect(page.getByTestId("miniapp-view-frame")).toHaveCount(0);
165+
expect(
166+
await page.evaluate(() =>
167+
window.__fixture.requests.filter((request) => request.operation === "openMiniApp").length,
168+
),
169+
).toBe(1);
170+
});
171+
140172
test("a stopped Mini App clears its view and reopens only when the person asks", async ({ page }) => {
141173
await configureFixture(page, () => {
142174
window.__WEBUI_FIXTURE_SETUP__ ??= [];

0 commit comments

Comments
 (0)