Summary
The miniapp tool is never offered to the agent in a web personal DM, even though the web UI can render playground artifacts in DMs. Asking for a miniapp in a personal web conversation makes the agent reply that it has no miniapp tool (it only sees execute, read, write, publish, memory, history, background, finish_silently, goals), so it falls back to writing a file or attempting a publish instead.
Repro
- Run core + web-ui (org
acme, runtime pi).
- In the web UI, open your personal DM (
web:<user>:default, scope personal:<user>).
- Send: "做一个 miniapp 演示" / "make a miniapp demo".
- Agent reads
skills/miniapp/SKILL.md, then states the miniapp tool is missing from its function list and never emits it.
Observed agent toolset in a web DM turn: execute, read, write, publish, memory, history, background, finish_silently, create_goal, get_goal, update_goal — no miniapp.
In a web channel/group conversation the tool does appear (the same ask works).
Root cause
The tool gating in src/harness/pi-tools.ts is correct:
...(opts?.surfaceName === "web" ? [miniapp] : []),
But surfaceName is only ever set when the turn is routed with surface tools, in src/core/orchestrator.ts (turn assembly):
...(input.surfaceTools && surfaceToolDeps
? { surfaceTools: true, surfaceName: /* "web" for web surfaces */ }
: {}),
And input.surfaceTools only becomes true via spine routing, src/api/app-ambient.ts:
function shouldRouteToSpine(input: OrchestratorInput): boolean {
const { conversation } = input;
return conversation.kind !== "dm" && (input.origin.kind === "human" || input.origin.kind === "ambient");
}
src/api/app-turn.ts only sets surfaceTools: true when req.surfaceTools is set or spineRouted.
So for a web conversation with conversation.kind === "dm", surfaceTools is always false → surfaceName is never set → miniapp is never exposed, even though the surface is "web" and the web UI (plugins/web-ui playground.ts + /api/playgrounds/:id with sandbox CSP) renders playgrounds in any session, DM included. Channel/group web chats work only because they go through the spine path.
Suggested fix
Decouple surfaceName from surfaceTools in orchestrator turn assembly so web surfaces always carry surfaceName: "web":
- ...(input.surfaceTools && surfaceToolDeps
+ ...(input.surfaceTools && surfaceToolDeps ? { surfaceTools: true } : {}),
+ ...(input.surface === "web" || (input.surfaceTools && surfaceToolDeps)
? {
- surfaceTools: true,
surfaceName:
input.origin.kind === "automation" && input.origin.destination
? "slack"
: (input.surface ?? "slack"),
}
: {}),
surfaceTools still gates the surface post tools as before; only the web surfaceName (which additionally enables miniapp) is now available in web DMs. Non-web turns without surface tools are unaffected (their behavior already defaults to surfaceName ?? "slack" inside pi-tools).
Verified locally with this change: a web DM turn exposes miniapp, the tool call succeeds, and GET /api/playgrounds/:artifactId serves the artifact with the sandbox CSP.
Environment
- Repo: yc-software/qm @
b384c65
- Surface: web-ui DM, org runtime
pi + OpenAI-compatible provider
Summary
The
miniapptool is never offered to the agent in a web personal DM, even though the web UI can render playground artifacts in DMs. Asking for a miniapp in a personal web conversation makes the agent reply that it has nominiapptool (it only seesexecute,read,write,publish,memory,history,background,finish_silently, goals), so it falls back to writing a file or attempting a publish instead.Repro
acme, runtimepi).web:<user>:default, scopepersonal:<user>).skills/miniapp/SKILL.md, then states theminiapptool is missing from its function list and never emits it.Observed agent toolset in a web DM turn:
execute, read, write, publish, memory, history, background, finish_silently, create_goal, get_goal, update_goal— nominiapp.In a web channel/group conversation the tool does appear (the same ask works).
Root cause
The tool gating in
src/harness/pi-tools.tsis correct:But
surfaceNameis only ever set when the turn is routed with surface tools, insrc/core/orchestrator.ts(turn assembly):And
input.surfaceToolsonly becomes true via spine routing,src/api/app-ambient.ts:src/api/app-turn.tsonly setssurfaceTools: truewhenreq.surfaceToolsis set orspineRouted.So for a web conversation with
conversation.kind === "dm",surfaceToolsis always false →surfaceNameis never set →miniappis never exposed, even though the surface is"web"and the web UI (plugins/web-uiplayground.ts+/api/playgrounds/:idwith sandbox CSP) renders playgrounds in any session, DM included. Channel/group web chats work only because they go through the spine path.Suggested fix
Decouple
surfaceNamefromsurfaceToolsin orchestrator turn assembly so web surfaces always carrysurfaceName: "web":surfaceToolsstill gates the surface post tools as before; only the websurfaceName(which additionally enablesminiapp) is now available in web DMs. Non-web turns without surface tools are unaffected (their behavior already defaults tosurfaceName ?? "slack"inside pi-tools).Verified locally with this change: a web DM turn exposes
miniapp, the tool call succeeds, andGET /api/playgrounds/:artifactIdserves the artifact with the sandbox CSP.Environment
b384c65pi+ OpenAI-compatible provider