openpencil/apps
Fini ff7b736d90 feat(ai): Phase 2 element-tools dispatcher + client shim registry
Replaces orchestrator-sub-agent.ts's Phase 1 stub (which only
logged and errored on `<op_tool>` output) with a real apply path:

- element-tools-dispatcher.ts: dispatchElementToolCall(shape, ctx)
  runs the full pass inside one startBatch/endBatch pair so a
  generation collapses to a single undo entry. Routes element-tool
  calls through the shim registry; falls back to /api/mcp/exec-tool
  HTTP when shim misses. Validates parent_id existence, rejects
  filePath unless it is the "live://canvas" sentinel, rejects
  pageId that diverges from the active page, rejects stale
  defaultParentId — all with structured failure messages instead
  of silent drops with "applied" reports.

- element-tool-shims/: 10-tool registry backed by pen-core
  element-builders. wrap<T>() strips parent_id/pageId/filePath
  before invoking the builder and surfaces them on ElementShimResult
  so the dispatcher can honor them during insert. Same pen-core
  builders the server-side pen-mcp handler uses — drift impossible.

- orchestrator-sub-agent.ts: invokes dispatcher with
  defaultParentId = subtask.parentFrameId ?? plan.rootFrame.id,
  mirroring StreamingDesignRenderer's construction so rootless
  payloads land inside the generation's target frame. DispatchResult
  carries insertedNodes[] and the orchestrator uses them to update
  progressEntry.nodeCount / progress.totalNodes / onApplyPartial
  so a successful element-tool subtask does not look like a failure
  to upstream accounting.

- Dispatcher uses insertStreamingNode (not raw addNode) so element
  tool output goes through the same canonical path the streaming
  renderer uses: id collision guard, parent remap, layout-aware
  child normalization, phone-placeholder guards, append semantics,
  and auto expandRootFrameHeight.

13 tests lock invariants: batch wrap fires exactly once per
dispatch (including back-to-back), applied/failed/unsupported
return shapes, parent_id / pageId / filePath / defaultParentId
validation branches, shim-hit success, HTTP fallback path under
fetch failure, "stale default + valid payload parent_id" payload-wins
precedence.
2026-04-21 22:04:21 +08:00
..
cli chore(release): bump to v0.7.4 2026-04-17 19:20:19 +08:00
desktop test(electron): unblock desktop/git tests on git 2.36 + stubbed electron 2026-04-20 01:22:28 +08:00
web feat(ai): Phase 2 element-tools dispatcher + client shim registry 2026-04-21 22:04:21 +08:00