@tangle-network/agent-app 0.43.56 → 0.43.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DesignCanvas-UNL34LS2.js +12 -0
- package/dist/DesignCanvasEditor-FJIVYOA2.js +13 -0
- package/dist/IntakeInterview-3ZIQMZS6.js +8 -0
- package/dist/InvitationsPanel-CJ7DR4F7.js +8 -0
- package/dist/MembersPanel-BB4OD2KB.js +8 -0
- package/dist/{TimelineEditor-I35SJJAJ.js → TimelineEditor-2ECKSU6P.js} +3 -3
- package/dist/{VaultPane-DrQ_tH6c.d.ts → VaultPane-BDP6SB_K.d.ts} +1 -0
- package/dist/{access-BZ81Btt5.d.ts → access-490O6aOU.d.ts} +16 -1
- package/dist/app-auth/index.d.ts +6 -1
- package/dist/app-auth/index.js +3 -3
- package/dist/app-auth/index.js.map +1 -1
- package/dist/{apply-Gk4raT2j.d.ts → apply-6wlMOLf8.d.ts} +30 -1
- package/dist/assets/index.d.ts +41 -0
- package/dist/assets/index.js +1 -1
- package/dist/assistant/index.d.ts +26 -6
- package/dist/assistant/index.js +9 -9
- package/dist/assistant/index.js.map +1 -1
- package/dist/{attachment-validation-D0csjCvz.d.ts → attachment-validation-tPRp-PP1.d.ts} +7 -1
- package/dist/{auth-DuptSkWh.d.ts → auth-_FU8w01b.d.ts} +4 -1
- package/dist/billing/index.d.ts +8 -0
- package/dist/billing/index.js +1 -1
- package/dist/brand/index.d.ts +2 -0
- package/dist/brand/index.js.map +1 -1
- package/dist/brand-extraction/index.d.ts +3 -0
- package/dist/brand-extraction/index.js.map +1 -1
- package/dist/catalog/index.d.ts +2 -0
- package/dist/catalog/index.js +1 -1
- package/dist/chat-routes/index.d.ts +41 -10
- package/dist/chat-routes/index.js +15 -15
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +18 -4
- package/dist/chat-store/index.js +4 -4
- package/dist/chat-store/index.js.map +1 -1
- package/dist/{chunk-H5FM75MR.js → chunk-22GLESKE.js} +3 -3
- package/dist/chunk-22GLESKE.js.map +1 -0
- package/dist/{chunk-WEBBJBDH.js → chunk-2DRYTJHI.js} +1 -1
- package/dist/chunk-2DRYTJHI.js.map +1 -0
- package/dist/{chunk-7EVZUIHW.js → chunk-3EJ6SFJI.js} +1 -1
- package/dist/chunk-3EJ6SFJI.js.map +1 -0
- package/dist/{chunk-HRJJH3RX.js → chunk-3OFQFW5K.js} +4 -4
- package/dist/chunk-3OFQFW5K.js.map +1 -0
- package/dist/{chunk-7SUPA2YT.js → chunk-4KKXZQPT.js} +2 -2
- package/dist/chunk-4KKXZQPT.js.map +1 -0
- package/dist/{chunk-5PTGEJZL.js → chunk-5SD6HT2V.js} +1 -1
- package/dist/chunk-5SD6HT2V.js.map +1 -0
- package/dist/{chunk-7C64WKAH.js → chunk-5SXS3YAB.js} +2 -2
- package/dist/{chunk-L2TG5DBW.js → chunk-5TESA4SN.js} +1 -1
- package/dist/chunk-5TESA4SN.js.map +1 -0
- package/dist/{chunk-63CE7FEZ.js → chunk-6XIAPIW6.js} +1 -1
- package/dist/chunk-6XIAPIW6.js.map +1 -0
- package/dist/{chunk-MFRCM32T.js → chunk-7CTIUCQ4.js} +1 -1
- package/dist/chunk-7CTIUCQ4.js.map +1 -0
- package/dist/{chunk-6KVH5SP5.js → chunk-7YW6EBDX.js} +2 -2
- package/dist/chunk-7YW6EBDX.js.map +1 -0
- package/dist/{chunk-ZXNXAQAH.js → chunk-AP2UXKUK.js} +1 -1
- package/dist/chunk-AP2UXKUK.js.map +1 -0
- package/dist/{chunk-K63OWUDJ.js → chunk-BPSV5AA2.js} +2 -2
- package/dist/chunk-BPSV5AA2.js.map +1 -0
- package/dist/{chunk-MFI4ZM7W.js → chunk-BT4QFCTB.js} +1 -1
- package/dist/chunk-BT4QFCTB.js.map +1 -0
- package/dist/{chunk-ZYBWGSAZ.js → chunk-BVKQKGRK.js} +1 -1
- package/dist/chunk-BVKQKGRK.js.map +1 -0
- package/dist/{chunk-RVUISYUE.js → chunk-C3NSB66C.js} +7 -7
- package/dist/chunk-C3NSB66C.js.map +1 -0
- package/dist/{chunk-BQV42AFK.js → chunk-CCVWR33L.js} +1 -1
- package/dist/chunk-CCVWR33L.js.map +1 -0
- package/dist/{chunk-MCJSS6SM.js → chunk-CQZSAR77.js} +1 -1
- package/dist/chunk-CQZSAR77.js.map +1 -0
- package/dist/{chunk-3SVAA3MA.js → chunk-DJ4VJIH5.js} +1 -1
- package/dist/chunk-DJ4VJIH5.js.map +1 -0
- package/dist/{chunk-XKBIYSSM.js → chunk-DTSPS2HV.js} +3 -3
- package/dist/chunk-DTSPS2HV.js.map +1 -0
- package/dist/{chunk-4QN75UHX.js → chunk-E7N67FYI.js} +8 -8
- package/dist/chunk-E7N67FYI.js.map +1 -0
- package/dist/{chunk-2QI7XV2T.js → chunk-FBVLEGEG.js} +1 -1
- package/dist/{chunk-2QI7XV2T.js.map → chunk-FBVLEGEG.js.map} +1 -1
- package/dist/{chunk-5RMIUJDI.js → chunk-FKCB5FTD.js} +1 -1
- package/dist/chunk-FKCB5FTD.js.map +1 -0
- package/dist/{chunk-EEPJGZJW.js → chunk-GPBRL2XR.js} +1 -1
- package/dist/chunk-GPBRL2XR.js.map +1 -0
- package/dist/{chunk-G3HCU7TA.js → chunk-GXE6KHTS.js} +1 -1
- package/dist/chunk-GXE6KHTS.js.map +1 -0
- package/dist/{chunk-65P3HJY3.js → chunk-HCOROIRT.js} +1 -1
- package/dist/chunk-HCOROIRT.js.map +1 -0
- package/dist/{chunk-54GSK2KK.js → chunk-HYTIHYNV.js} +2 -2
- package/dist/chunk-HYTIHYNV.js.map +1 -0
- package/dist/{chunk-5MG74GVQ.js → chunk-IG46XCZM.js} +3 -3
- package/dist/chunk-IG46XCZM.js.map +1 -0
- package/dist/{chunk-ND3XEGBE.js → chunk-JDEGS53O.js} +1 -1
- package/dist/chunk-JDEGS53O.js.map +1 -0
- package/dist/{chunk-7W5XSTUF.js → chunk-JML7WKWU.js} +1 -1
- package/dist/chunk-JML7WKWU.js.map +1 -0
- package/dist/{chunk-Q4TKVF3L.js → chunk-JTLYXUEV.js} +1 -1
- package/dist/chunk-JTLYXUEV.js.map +1 -0
- package/dist/{chunk-CCRVDLV3.js → chunk-JWBZ74TW.js} +2 -2
- package/dist/chunk-JWBZ74TW.js.map +1 -0
- package/dist/{chunk-ZU5GNSOJ.js → chunk-K3LIPKFV.js} +3 -3
- package/dist/chunk-K3LIPKFV.js.map +1 -0
- package/dist/{chunk-GQRL6VNU.js → chunk-LGBO24FL.js} +1 -1
- package/dist/chunk-LGBO24FL.js.map +1 -0
- package/dist/{chunk-SWUVTGMR.js → chunk-MEUNTJL5.js} +3 -3
- package/dist/{chunk-SWUVTGMR.js.map → chunk-MEUNTJL5.js.map} +1 -1
- package/dist/{chunk-YNALYIQ3.js → chunk-NLSSFLLS.js} +1 -1
- package/dist/chunk-NLSSFLLS.js.map +1 -0
- package/dist/{chunk-HFC4BTWJ.js → chunk-PTUMBMVH.js} +1 -1
- package/dist/chunk-PTUMBMVH.js.map +1 -0
- package/dist/{chunk-R4NOKZMD.js → chunk-ROLFDOMJ.js} +3 -3
- package/dist/{chunk-NYATNLRK.js → chunk-RT5RKAFX.js} +1 -1
- package/dist/chunk-RT5RKAFX.js.map +1 -0
- package/dist/{chunk-GNL3MG5J.js → chunk-S564OFTL.js} +2 -2
- package/dist/{chunk-GUSCYE3S.js → chunk-TQOFR6XY.js} +1 -1
- package/dist/chunk-TQOFR6XY.js.map +1 -0
- package/dist/{chunk-47C6PJCW.js → chunk-TZO3SOUL.js} +1 -1
- package/dist/chunk-TZO3SOUL.js.map +1 -0
- package/dist/{chunk-YGUNTIT5.js → chunk-UKTYKG57.js} +1 -1
- package/dist/chunk-UKTYKG57.js.map +1 -0
- package/dist/{chunk-23LUHHHN.js → chunk-VCOD5AOR.js} +1 -1
- package/dist/chunk-VCOD5AOR.js.map +1 -0
- package/dist/{chunk-5I72FRZY.js → chunk-VOJB7MVG.js} +2 -2
- package/dist/{chunk-XWJXZ6WE.js → chunk-VTWKH4YU.js} +2 -2
- package/dist/chunk-VTWKH4YU.js.map +1 -0
- package/dist/{chunk-4K7BZYO7.js → chunk-VWAEWOWK.js} +3 -3
- package/dist/chunk-VWAEWOWK.js.map +1 -0
- package/dist/{chunk-M2EBUDZ7.js → chunk-VWOPOLVO.js} +2 -2
- package/dist/chunk-VWOPOLVO.js.map +1 -0
- package/dist/{chunk-BN2USWIR.js → chunk-VYECQAGM.js} +2 -2
- package/dist/chunk-VYECQAGM.js.map +1 -0
- package/dist/{chunk-SIXYZ2FB.js → chunk-YJMCRXQQ.js} +1 -1
- package/dist/chunk-YJMCRXQQ.js.map +1 -0
- package/dist/{chunk-PJC4NXPA.js → chunk-YKBDH2UY.js} +3 -3
- package/dist/chunk-YKBDH2UY.js.map +1 -0
- package/dist/{chunk-XAWFPMAR.js → chunk-YKOM4ZLQ.js} +1 -1
- package/dist/chunk-YKOM4ZLQ.js.map +1 -0
- package/dist/{chunk-TKVJE63N.js → chunk-YRMLYFVR.js} +3 -3
- package/dist/chunk-YRMLYFVR.js.map +1 -0
- package/dist/{chunk-USYXHDKE.js → chunk-YZ6KQULN.js} +2 -2
- package/dist/{command-stack-B_-WNfZU.d.ts → command-stack-TNPWJCzW.d.ts} +14 -2
- package/dist/{completion-Kaqp_tWk.d.ts → completion-CvcNUz07.d.ts} +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/{contract-KfqJh_au.d.ts → contract-TjB68uqA.d.ts} +17 -0
- package/dist/design-canvas/drizzle.d.ts +10 -2
- package/dist/design-canvas/drizzle.js.map +1 -1
- package/dist/design-canvas/index.d.ts +29 -11
- package/dist/design-canvas/index.js +1070 -17
- package/dist/design-canvas/index.js.map +1 -1
- package/dist/design-canvas-react/engine.d.ts +42 -6
- package/dist/design-canvas-react/engine.js +4 -4
- package/dist/design-canvas-react/index.d.ts +12 -7
- package/dist/design-canvas-react/index.js +11 -11
- package/dist/design-canvas-react/index.js.map +1 -1
- package/dist/design-canvas-react/lazy.d.ts +4 -4
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/durable-chat/index.d.ts +46 -1
- package/dist/durable-chat/index.js +3 -3
- package/dist/eval/index.d.ts +1 -1
- package/dist/{export-presets-DAsU5Wal.d.ts → export-presets-mgVulRaV.d.ts} +10 -1
- package/dist/{flow-types-Cb_AblZs.d.ts → flow-types-CqomVAUN.d.ts} +1 -0
- package/dist/harness/index.d.ts +5 -0
- package/dist/harness/index.js +1 -1
- package/dist/index.d.ts +10 -18
- package/dist/index.js +30 -231
- package/dist/intakes/api.d.ts +3 -2
- package/dist/intakes/api.js +3 -3
- package/dist/intakes/api.js.map +1 -1
- package/dist/intakes/drizzle.d.ts +10 -2
- package/dist/intakes/drizzle.js +3 -3
- package/dist/intakes/drizzle.js.map +1 -1
- package/dist/intakes/index.d.ts +2 -2
- package/dist/intakes/index.js +2 -2
- package/dist/intakes-react/index.d.ts +2 -1
- package/dist/intakes-react/index.js +2 -2
- package/dist/intakes-react/lazy.d.ts +2 -1
- package/dist/intakes-react/lazy.js +1 -1
- package/dist/intakes-react/lazy.js.map +1 -1
- package/dist/integrations/index.d.ts +4 -0
- package/dist/integrations/index.js +1 -1
- package/dist/interactions/index.d.ts +13 -2
- package/dist/interactions/index.js +2 -2
- package/dist/{invitations-CZ5G2tIn.d.ts → invitations-DoYMvXea.d.ts} +11 -1
- package/dist/{invitations-schema-DaMO4hZE.d.ts → invitations-schema-_fI7HkJP.d.ts} +5 -1
- package/dist/knowledge/index.d.ts +2 -0
- package/dist/knowledge/index.js +1 -1
- package/dist/knowledge-loop/index.d.ts +4 -1
- package/dist/knowledge-loop/index.js +1 -1
- package/dist/{lazy-C7LiVeFt.d.ts → lazy-DrkVyDpe.d.ts} +2 -2
- package/dist/{mcp-BHfIoGLW.d.ts → mcp-BjPqQ-3C.d.ts} +4 -2
- package/dist/{mcp-rpc-DLw_r9PQ.d.ts → mcp-rpc-BfUOsalw.d.ts} +3 -0
- package/dist/missions/index.d.ts +25 -0
- package/dist/missions/index.js +1 -1
- package/dist/{model-Bzi5i63l.d.ts → model-C8U0HZxL.d.ts} +1 -0
- package/dist/{model-dF2h4xT9.d.ts → model-DmdkIteM.d.ts} +19 -0
- package/dist/{model-BBwyOuWG.d.ts → model-DnfO_PdJ.d.ts} +25 -1
- package/dist/model-resolution/index.d.ts +10 -0
- package/dist/model-resolution/index.js.map +1 -1
- package/dist/{operations-DLIvvD00.d.ts → operations-ByJ6yXOW.d.ts} +20 -2
- package/dist/{parts-1sRYSPjF.d.ts → parts-n9XUon0a.d.ts} +29 -1
- package/dist/plans/index.d.ts +6 -0
- package/dist/plans/index.js +1 -1
- package/dist/platform/index.d.ts +24 -2
- package/dist/platform/index.js +3 -3
- package/dist/platform/index.js.map +1 -1
- package/dist/preflight/index.d.ts +3 -0
- package/dist/preflight/index.js +1 -1
- package/dist/preset-cloudflare/index.d.ts +4 -1
- package/dist/preset-cloudflare/index.js +2 -2
- package/dist/redact/index.d.ts +5 -0
- package/dist/redact/index.js +1 -1
- package/dist/{roles-BC1n4t37.d.ts → roles-B4nU3jDi.d.ts} +12 -0
- package/dist/run/index.d.ts +3 -0
- package/dist/run/index.js +1 -1
- package/dist/run/index.js.map +1 -1
- package/dist/runtime/index.d.ts +11 -4
- package/dist/runtime/index.js +4 -4
- package/dist/sandbox/index.d.ts +96 -3
- package/dist/sandbox/index.js +6 -6
- package/dist/{sandbox-terminal-BIIC__CP.d.ts → sandbox-terminal-ChNEdHF8.d.ts} +5 -0
- package/dist/sequences/drizzle.d.ts +11 -1
- package/dist/sequences/drizzle.js +1 -1
- package/dist/sequences/drizzle.js.map +1 -1
- package/dist/sequences/index.d.ts +32 -7
- package/dist/sequences/index.js +1562 -47
- package/dist/sequences/index.js.map +1 -1
- package/dist/sequences-react/index.d.ts +32 -2
- package/dist/sequences-react/index.js +3 -3
- package/dist/sequences-react/index.js.map +1 -1
- package/dist/{sso-CNOsARMJ.d.ts → sso-Cm00S61u.d.ts} +13 -0
- package/dist/{stale-turn-lock-C8Na1cFZ.d.ts → stale-turn-lock-DucQzvXu.d.ts} +2 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.js.map +1 -1
- package/dist/{store-gckrNq-g.d.ts → store-B7LLlk9p.d.ts} +22 -1
- package/dist/{store-CFyxgTlD.d.ts → store-CqfDtnPQ.d.ts} +8 -2
- package/dist/stream/index.d.ts +10 -4
- package/dist/stream/index.js +4 -4
- package/dist/{stream-normalizer-DWvtmY6F.d.ts → stream-normalizer-C3M--r6y.d.ts} +17 -1
- package/dist/studio/index.d.ts +37 -0
- package/dist/studio/index.js +1 -1
- package/dist/studio-react/index.d.ts +3 -0
- package/dist/studio-react/index.js +1 -1
- package/dist/studio-react/index.js.map +1 -1
- package/dist/tangle/index.d.ts +3 -0
- package/dist/tangle/index.js +1 -1
- package/dist/teams/drizzle.d.ts +8 -4
- package/dist/teams/drizzle.js +1 -1
- package/dist/teams/drizzle.js.map +1 -1
- package/dist/teams/index.d.ts +4 -2
- package/dist/teams/index.js +3 -3
- package/dist/teams/invitations-api.d.ts +10 -4
- package/dist/teams/invitations-api.js +4 -4
- package/dist/teams/invitations-api.js.map +1 -1
- package/dist/teams/members-api.d.ts +4 -2
- package/dist/teams/members-api.js +3 -3
- package/dist/teams/resend.d.ts +5 -4
- package/dist/teams/resend.js +1 -1
- package/dist/teams/resend.js.map +1 -1
- package/dist/teams-react/index.d.ts +7 -2
- package/dist/teams-react/index.js +3 -3
- package/dist/teams-react/lazy.d.ts +5 -2
- package/dist/teams-react/lazy.js +2 -2
- package/dist/teams-react/lazy.js.map +1 -1
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/index.js +1 -1
- package/dist/theme/tailwind-preset.d.ts +1 -0
- package/dist/theme/tailwind-preset.js.map +1 -1
- package/dist/theme-contract/cli.js +1 -1
- package/dist/theme-contract/index.d.ts +3 -0
- package/dist/theme-contract/index.js +1 -1
- package/dist/tools/index.d.ts +13 -6
- package/dist/tools/index.js +3 -3
- package/dist/trace/index.d.ts +6 -1
- package/dist/trace/index.js +2 -2
- package/dist/{turn-buffer-C9mEgoop.d.ts → turn-buffer-DGnAPKwa.d.ts} +6 -0
- package/dist/turn-stream/index.d.ts +28 -2
- package/dist/turn-stream/index.js +1 -1
- package/dist/turn-stream/index.js.map +1 -1
- package/dist/{types-BEOvc_ue.d.ts → types-CBRyqijY.d.ts} +10 -0
- package/dist/vault/index.d.ts +1 -1
- package/dist/vault/lazy.d.ts +3 -2
- package/dist/vault/lazy.js.map +1 -1
- package/dist/web/index.d.ts +4 -0
- package/dist/web/index.js +1 -1
- package/dist/web-react/index.d.ts +51 -8
- package/dist/web-react/index.js +9 -9
- package/dist/web-react/terminal.d.ts +2 -2
- package/dist/web-react/terminal.js +1 -1
- package/package.json +3 -2
- package/dist/DesignCanvas-5DJAFDNC.js +0 -12
- package/dist/DesignCanvasEditor-XUJZARSN.js +0 -13
- package/dist/IntakeInterview-WHVKMNQW.js +0 -8
- package/dist/InvitationsPanel-XUUWZBPV.js +0 -8
- package/dist/MembersPanel-FOVHWUQ3.js +0 -8
- package/dist/chunk-23LUHHHN.js.map +0 -1
- package/dist/chunk-3SVAA3MA.js.map +0 -1
- package/dist/chunk-47C6PJCW.js.map +0 -1
- package/dist/chunk-4K7BZYO7.js.map +0 -1
- package/dist/chunk-4QN75UHX.js.map +0 -1
- package/dist/chunk-54GSK2KK.js.map +0 -1
- package/dist/chunk-5MG74GVQ.js.map +0 -1
- package/dist/chunk-5PTGEJZL.js.map +0 -1
- package/dist/chunk-5RMIUJDI.js.map +0 -1
- package/dist/chunk-63CE7FEZ.js.map +0 -1
- package/dist/chunk-65P3HJY3.js.map +0 -1
- package/dist/chunk-6KVH5SP5.js.map +0 -1
- package/dist/chunk-7EVZUIHW.js.map +0 -1
- package/dist/chunk-7SUPA2YT.js.map +0 -1
- package/dist/chunk-7W5XSTUF.js.map +0 -1
- package/dist/chunk-BN2USWIR.js.map +0 -1
- package/dist/chunk-BQV42AFK.js.map +0 -1
- package/dist/chunk-CCRVDLV3.js.map +0 -1
- package/dist/chunk-EEPJGZJW.js.map +0 -1
- package/dist/chunk-G3HCU7TA.js.map +0 -1
- package/dist/chunk-GQRL6VNU.js.map +0 -1
- package/dist/chunk-GUSCYE3S.js.map +0 -1
- package/dist/chunk-H5FM75MR.js.map +0 -1
- package/dist/chunk-HFC4BTWJ.js.map +0 -1
- package/dist/chunk-HRJJH3RX.js.map +0 -1
- package/dist/chunk-K63OWUDJ.js.map +0 -1
- package/dist/chunk-L2TG5DBW.js.map +0 -1
- package/dist/chunk-M2EBUDZ7.js.map +0 -1
- package/dist/chunk-MCJSS6SM.js.map +0 -1
- package/dist/chunk-MFI4ZM7W.js.map +0 -1
- package/dist/chunk-MFRCM32T.js.map +0 -1
- package/dist/chunk-ND3XEGBE.js.map +0 -1
- package/dist/chunk-NEOV2NQ3.js +0 -1617
- package/dist/chunk-NEOV2NQ3.js.map +0 -1
- package/dist/chunk-NYATNLRK.js.map +0 -1
- package/dist/chunk-PJC4NXPA.js.map +0 -1
- package/dist/chunk-Q4TKVF3L.js.map +0 -1
- package/dist/chunk-RVUISYUE.js.map +0 -1
- package/dist/chunk-SIXYZ2FB.js.map +0 -1
- package/dist/chunk-TKVJE63N.js.map +0 -1
- package/dist/chunk-WEBBJBDH.js.map +0 -1
- package/dist/chunk-WFMUDX5J.js +0 -1092
- package/dist/chunk-WFMUDX5J.js.map +0 -1
- package/dist/chunk-XAWFPMAR.js.map +0 -1
- package/dist/chunk-XKBIYSSM.js.map +0 -1
- package/dist/chunk-XWJXZ6WE.js.map +0 -1
- package/dist/chunk-YGUNTIT5.js.map +0 -1
- package/dist/chunk-YNALYIQ3.js.map +0 -1
- package/dist/chunk-ZU5GNSOJ.js.map +0 -1
- package/dist/chunk-ZXNXAQAH.js.map +0 -1
- package/dist/chunk-ZYBWGSAZ.js.map +0 -1
- /package/dist/{DesignCanvas-5DJAFDNC.js.map → DesignCanvas-UNL34LS2.js.map} +0 -0
- /package/dist/{DesignCanvasEditor-XUJZARSN.js.map → DesignCanvasEditor-FJIVYOA2.js.map} +0 -0
- /package/dist/{IntakeInterview-WHVKMNQW.js.map → IntakeInterview-3ZIQMZS6.js.map} +0 -0
- /package/dist/{InvitationsPanel-XUUWZBPV.js.map → InvitationsPanel-CJ7DR4F7.js.map} +0 -0
- /package/dist/{MembersPanel-FOVHWUQ3.js.map → MembersPanel-BB4OD2KB.js.map} +0 -0
- /package/dist/{TimelineEditor-I35SJJAJ.js.map → TimelineEditor-2ECKSU6P.js.map} +0 -0
- /package/dist/{chunk-7C64WKAH.js.map → chunk-5SXS3YAB.js.map} +0 -0
- /package/dist/{chunk-R4NOKZMD.js.map → chunk-ROLFDOMJ.js.map} +0 -0
- /package/dist/{chunk-GNL3MG5J.js.map → chunk-S564OFTL.js.map} +0 -0
- /package/dist/{chunk-5I72FRZY.js.map → chunk-VOJB7MVG.js.map} +0 -0
- /package/dist/{chunk-USYXHDKE.js.map → chunk-YZ6KQULN.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/design-canvas/operations.ts","../src/design-canvas/templates.ts","../src/design-canvas/mcp-tools.ts","../src/design-canvas/mcp-handler.ts","../src/design-canvas/mcp-entry.ts"],"sourcesContent":["/**\n * The design-canvas operation union — the ONE mutation vocabulary shared by\n * the editor's command stack (human edits, undo/redo), the MCP tool\n * dispatcher (agent edits), and programmatic automation (template\n * instantiation, data sync). Every state change the editor can express is an\n * operation here; anything not expressible here does not happen to a\n * document. That closure is the automation contract: replaying a decision\n * log reproduces the document.\n *\n * Attribute patches are validated per element kind in ./validate before\n * ./apply mutates the document; both run server-side on every path.\n */\n\nimport type { NewPageOptions, PageBleed, PageGuides, SceneElement } from './model'\n\n/** Per-kind attribute patch. `kind` is immutable after creation; ids are\n * immutable always. Validation rejects attrs foreign to the target's kind. */\nexport type SceneAttrsPatch = Partial<Omit<SceneElement, 'id' | 'kind' | 'children'>> & {\n text?: string\n width?: number\n height?: number\n points?: number[]\n fill?: string\n stroke?: string\n strokeWidth?: number\n cornerRadius?: number\n dash?: number[]\n fontFamily?: string\n fontSize?: number\n fontStyle?: 'normal' | 'bold' | 'italic' | 'bold italic'\n align?: 'left' | 'center' | 'right'\n lineHeight?: number\n letterSpacing?: number\n src?: string\n posterSrc?: string\n fit?: 'fill' | 'cover' | 'contain'\n}\n\nexport interface AddElementOperation {\n type: 'add_element'\n pageId: string\n /** Complete element including caller-minted id (reconciled like sequences\n * clips when the server re-mints). */\n element: SceneElement\n /** Insertion z-index; omitted → top. */\n index?: number\n /** Parent group; omitted → page root. */\n parentGroupId?: string\n}\n\nexport interface SetAttrsOperation {\n type: 'set_attrs'\n pageId: string\n elementId: string\n attrs: SceneAttrsPatch\n}\n\nexport interface ReorderElementOperation {\n type: 'reorder_element'\n pageId: string\n elementId: string\n /** Target index within the element's CURRENT owner (page root or group). */\n toIndex: number\n}\n\nexport interface DeleteElementOperation {\n type: 'delete_element'\n pageId: string\n elementId: string\n}\n\nexport interface GroupElementsOperation {\n type: 'group_elements'\n pageId: string\n /** ≥ 2 sibling elements (same owner); grouped in their current z-order. */\n elementIds: string[]\n /** Caller-minted id for the new group. */\n groupId: string\n name?: string\n}\n\nexport interface UngroupElementOperation {\n type: 'ungroup_element'\n pageId: string\n groupId: string\n}\n\nexport interface AddPageOperation {\n type: 'add_page'\n /** Caller-minted page id. */\n pageId: string\n options?: NewPageOptions\n /** Position in the page list; omitted → end. */\n index?: number\n}\n\nexport interface DuplicatePageOperation {\n type: 'duplicate_page'\n sourcePageId: string\n /** Caller-minted id for the copy; element ids are re-minted server-side. */\n pageId: string\n}\n\nexport interface DeletePageOperation {\n type: 'delete_page'\n pageId: string\n}\n\nexport interface ReorderPageOperation {\n type: 'reorder_page'\n pageId: string\n toIndex: number\n}\n\nexport interface SetPagePropsOperation {\n type: 'set_page_props'\n pageId: string\n name?: string\n width?: number\n height?: number\n background?: string\n /** null clears bleed; omitted leaves unchanged. */\n bleed?: PageBleed | null\n}\n\nexport interface SetPageGuidesOperation {\n type: 'set_page_guides'\n pageId: string\n guides: PageGuides\n}\n\nexport interface BindSlotOperation {\n type: 'bind_slot'\n pageId: string\n elementId: string\n /** null unbinds. Slot names are unique document-wide. */\n slot: string | null\n}\n\n/** Fill slots with data — text slots take strings, image/video slots take\n * src URLs. Unknown slot names throw; partial application is allowed. */\nexport interface ApplyDataOperation {\n type: 'apply_data'\n bindings: Record<string, string>\n}\n\nexport interface SetDocumentTitleOperation {\n type: 'set_document_title'\n title: string\n}\n\nexport type SceneOperation =\n | AddElementOperation\n | SetAttrsOperation\n | ReorderElementOperation\n | DeleteElementOperation\n | GroupElementsOperation\n | UngroupElementOperation\n | AddPageOperation\n | DuplicatePageOperation\n | DeletePageOperation\n | ReorderPageOperation\n | SetPagePropsOperation\n | SetPageGuidesOperation\n | BindSlotOperation\n | ApplyDataOperation\n | SetDocumentTitleOperation\n\nexport interface ScenePlan {\n summary: string\n operations: SceneOperation[]\n}\n\nexport type SceneOperationType = SceneOperation['type']\n\nexport const SCENE_OPERATION_TYPES: readonly SceneOperationType[] = [\n 'add_element',\n 'set_attrs',\n 'reorder_element',\n 'delete_element',\n 'group_elements',\n 'ungroup_element',\n 'add_page',\n 'duplicate_page',\n 'delete_page',\n 'reorder_page',\n 'set_page_props',\n 'set_page_guides',\n 'bind_slot',\n 'apply_data',\n 'set_document_title',\n] as const\n","/**\n * Template helpers for design-canvas documents. A template is any document\n * whose elements carry `slot` names — those slots define the fillable surface\n * that data sources and agents target. The helpers here are pure (no store,\n * no Konva) so they run server-side, in MCP tools, or in tests without a DOM.\n *\n * Binding semantics (shared with `apply_data` operation):\n * - text elements: binding value replaces `element.text`\n * - image/video elements: binding value replaces `element.src`\n * - rect/ellipse elements with a slot: binding value replaces `element.fill`\n * (color-slot convention — background swatch templates)\n * - line/group slots are reserved; binding them throws a loud error\n * Unknown binding keys (no matching slot) throw — callers must preflight with\n * `validateBindings` or accept that the throw is the signal to fix their data.\n */\n\nimport {\n SCENE_SCHEMA_VERSION,\n type SceneDocument,\n type SceneElement,\n type ScenePage,\n type SceneElementKind,\n collectSlots,\n} from './model'\n\n// ---------------------------------------------------------------------------\n// Slot surface\n// ---------------------------------------------------------------------------\n\nexport type SlotFillKind = 'text' | 'src' | 'color'\n\nexport interface TemplateSlot {\n name: string\n pageId: string\n elementId: string\n elementKind: SceneElementKind\n fillKind: SlotFillKind\n}\n\n/**\n * Wraps `collectSlots` with kind-aware fill typing so callers know WHAT to\n * put in each slot without inspecting the element tree themselves.\n * Throws when duplicate slot names exist (propagated from collectSlots).\n */\nexport function listTemplateSlots(document: SceneDocument): TemplateSlot[] {\n const raw = collectSlots(document)\n const slots: TemplateSlot[] = []\n for (const [name, { pageId, elementId, kind }] of raw) {\n slots.push({ name, pageId, elementId, elementKind: kind, fillKind: fillKindForElementKind(kind) })\n }\n return slots\n}\n\nfunction fillKindForElementKind(kind: SceneElementKind): SlotFillKind {\n switch (kind) {\n case 'text': return 'text'\n case 'image':\n case 'video': return 'src'\n case 'rect':\n case 'ellipse': return 'color'\n case 'line':\n case 'group':\n throw new Error(\n `slot on \"${kind}\" element has no defined fill kind — bind_slot should not target line or group elements`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Binding validation\n// ---------------------------------------------------------------------------\n\n/**\n * Preflight check: every key in `bindings` must name a slot in the document.\n * Returns a list of problems; an empty array means the bindings are clean.\n * Does NOT throw — designed to run before `instantiateTemplate` so callers\n * can surface a structured error instead of catching.\n */\nexport function validateBindings(\n document: SceneDocument,\n bindings: Record<string, string>,\n): string[] {\n const slots = collectSlots(document)\n const problems: string[] = []\n for (const key of Object.keys(bindings)) {\n if (!slots.has(key)) {\n problems.push(`binding key \"${key}\" does not match any slot in the document`)\n }\n }\n return problems\n}\n\n// ---------------------------------------------------------------------------\n// Template instantiation\n// ---------------------------------------------------------------------------\n\nexport interface InstantiateOptions {\n /** Human-readable title for the new document. */\n title: string\n /** Slot bindings to apply after id re-minting. Partial application allowed. */\n bindings?: Record<string, string>\n /**\n * Caller-supplied id factory — every page/element id in the source document\n * is replaced with a fresh value from this callback. The callback receives\n * the source id so implementations can build stable deterministic ids (e.g.\n * `crypto.randomUUID()` or `nanoid()` from the host).\n */\n mintId(sourceId: string): string\n}\n\n/**\n * Produces a new `SceneDocument` from a template:\n * 1. Re-mints every page and element id via `options.mintId` (slot *names* are\n * preserved so apply_data still targets them by name).\n * 2. Applies `options.bindings` with the same semantics as `apply_data`.\n * 3. Stamps `metadata.templateSourceId` with the source document's title so\n * the lineage is traceable without storing separate template provenance rows.\n *\n * Throws when bindings reference unknown slots (validated via `validateBindings`\n * before mutation so no partial state is possible).\n */\nexport function instantiateTemplate(\n document: SceneDocument,\n options: InstantiateOptions,\n): SceneDocument {\n const bindings = options.bindings ?? {}\n\n // Preflight — throw before touching the document tree\n const problems = validateBindings(document, bindings)\n if (problems.length > 0) {\n throw new Error(`template bindings are invalid:\\n${problems.map((p) => ` - ${p}`).join('\\n')}`)\n }\n\n // Build id mapping: sourceId → mintedId. Walk all page + element ids now so\n // group children can reference their parent's minted id during deep copy.\n const idMap = new Map<string, string>()\n const allocate = (sourceId: string): string => {\n if (idMap.has(sourceId)) return idMap.get(sourceId)!\n const minted = options.mintId(sourceId)\n idMap.set(sourceId, minted)\n return minted\n }\n\n for (const page of document.pages) {\n allocate(page.id)\n collectElementIds(page.elements, allocate)\n }\n\n // Deep-copy pages with re-minted ids\n const newPages: ScenePage[] = document.pages.map((page) => ({\n ...page,\n id: idMap.get(page.id)!,\n elements: copyElements(page.elements, idMap),\n }))\n\n const newDocument: SceneDocument = {\n schemaVersion: SCENE_SCHEMA_VERSION,\n title: options.title,\n pages: newPages,\n settings: { ...document.settings },\n metadata: {\n ...document.metadata,\n templateSourceId: document.title,\n },\n }\n\n // Apply bindings against the copied document (ids are now minted, slots preserved)\n return Object.keys(bindings).length > 0 ? applyBindings(newDocument, bindings) : newDocument\n}\n\n// ---------------------------------------------------------------------------\n// Binding application (shared with apply_data operation semantics)\n// ---------------------------------------------------------------------------\n\n/**\n * Applies slot bindings to a document in place (mutates a deep copy produced\n * by the caller). Unknown slot names throw — the preflight in `instantiateTemplate`\n * guarantees this is unreachable there; exported so the `apply_data` operation\n * handler can reuse it without duplicating the switch.\n */\nexport function applyBindingsToDocument(\n document: SceneDocument,\n bindings: Record<string, string>,\n): SceneDocument {\n const problems = validateBindings(document, bindings)\n if (problems.length > 0) {\n throw new Error(`apply_data bindings are invalid:\\n${problems.map((p) => ` - ${p}`).join('\\n')}`)\n }\n return applyBindings(document, bindings)\n}\n\n// Internal — called only after validateBindings passes\nfunction applyBindings(document: SceneDocument, bindings: Record<string, string>): SceneDocument {\n const slots = collectSlots(document)\n // Build a lookup: elementId → { slot, value } so the walk is O(elements)\n const targetMap = new Map<string, { slotName: string; value: string }>()\n for (const [slotName, { elementId }] of slots) {\n const value = bindings[slotName]\n if (value !== undefined) {\n targetMap.set(elementId, { slotName, value })\n }\n }\n\n const newPages: ScenePage[] = document.pages.map((page) => ({\n ...page,\n elements: applyBindingsToElements(page.elements, targetMap),\n }))\n\n return { ...document, pages: newPages }\n}\n\nfunction applyBindingsToElements(\n elements: SceneElement[],\n targetMap: Map<string, { slotName: string; value: string }>,\n): SceneElement[] {\n return elements.map((element) => {\n const target = targetMap.get(element.id)\n let updated = element\n\n if (target !== undefined) {\n updated = applyBindingToElement(element, target.value, target.slotName)\n }\n\n if (updated.kind === 'group') {\n return { ...updated, children: applyBindingsToElements(updated.children, targetMap) }\n }\n return updated\n })\n}\n\nfunction applyBindingToElement(element: SceneElement, value: string, slotName: string): SceneElement {\n switch (element.kind) {\n case 'text': return { ...element, text: value }\n case 'image': return { ...element, src: value }\n case 'video': return { ...element, src: value }\n case 'rect': return { ...element, fill: value }\n case 'ellipse': return { ...element, fill: value }\n case 'line':\n case 'group':\n throw new Error(\n `slot \"${slotName}\" on \"${element.kind}\" element cannot accept a binding — remove the slot or use a supported element kind`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Deep copy helpers\n// ---------------------------------------------------------------------------\n\nfunction collectElementIds(elements: SceneElement[], allocate: (id: string) => string): void {\n for (const element of elements) {\n allocate(element.id)\n if (element.kind === 'group') collectElementIds(element.children, allocate)\n }\n}\n\nfunction copyElements(elements: SceneElement[], idMap: Map<string, string>): SceneElement[] {\n return elements.map((element) => copyElement(element, idMap))\n}\n\nfunction copyElement(element: SceneElement, idMap: Map<string, string>): SceneElement {\n const newId = idMap.get(element.id)\n if (newId === undefined) throw new Error(`element ${element.id} was not pre-allocated in the id map — this is a bug in instantiateTemplate`)\n const base = { ...element, id: newId }\n if (base.kind === 'group') {\n return { ...base, children: copyElements(base.children, idMap) }\n }\n return base\n}\n","/**\n * Design-canvas MCP tool registry — what the in-sandbox agent sees over the\n * live agent→canvas channel. Each entry carries an LLM-facing description, a\n * JSON Schema for the arguments, and the typed dispatch that validates the\n * resulting operations, applies them through the store, and records ONE\n * decision row per mutating call.\n *\n * Every mutation: validate → apply via storeApplyScenePlan → recordDecision.\n * Throws anywhere become isError results the model reads to correct its call.\n *\n * Convenience sugar tools (add_text, move_element, etc.) expand to the\n * underlying operation primitives before reaching the kernel — no separate\n * code path to drift.\n */\n\nimport { elementAabb, requirePage, SCENE_ELEMENT_KINDS } from './model'\nimport type {\n RectElement,\n EllipseElement,\n LineElement,\n TextElement,\n ImageElement,\n VideoElement,\n SceneDocument,\n SceneElement,\n ScenePage,\n} from './model'\nimport type { SceneOperation, ScenePlan } from './operations'\nimport type { SceneStore } from './store'\nimport { storeApplyScenePlan } from './apply'\nimport type { McpToolDefinition } from '../tools/mcp-rpc'\n\n// ---------------------------------------------------------------------------\n// Tool env\n// ---------------------------------------------------------------------------\n\nexport interface DesignCanvasMcpToolEnv {\n store: SceneStore\n mintId: () => string\n}\n\n// ---------------------------------------------------------------------------\n// Argument readers — fail loud with name + expected type\n// ---------------------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction requireString(args: Record<string, unknown>, name: string): string {\n const value = args[name]\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`${name} is required and must be a non-empty string`)\n }\n return value\n}\n\nfunction optionalString(args: Record<string, unknown>, name: string): string | undefined {\n const value = args[name]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string') throw new Error(`${name} must be a string when provided`)\n return value\n}\n\nfunction requireNumber(args: Record<string, unknown>, name: string): number {\n const value = args[name]\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`${name} is required and must be a finite number`)\n }\n return value\n}\n\nfunction optionalNumber(args: Record<string, unknown>, name: string): number | undefined {\n const value = args[name]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`${name} must be a finite number when provided`)\n }\n return value\n}\n\nfunction optionalBoolean(args: Record<string, unknown>, name: string): boolean | undefined {\n const value = args[name]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'boolean') throw new Error(`${name} must be a boolean when provided`)\n return value\n}\n\nfunction optionalNonNegativeInteger(args: Record<string, unknown>, name: string): number | undefined {\n const value = args[name]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n throw new Error(`${name} must be a non-negative integer when provided`)\n }\n return value\n}\n\nfunction requireStringArray(args: Record<string, unknown>, name: string): string[] {\n const value = args[name]\n if (!Array.isArray(value) || value.length === 0 || value.some((v) => typeof v !== 'string')) {\n throw new Error(`${name} is required and must be a non-empty array of strings`)\n }\n return value as string[]\n}\n\nfunction optionalRecord(args: Record<string, unknown>, name: string): Record<string, string> | undefined {\n const value = args[name]\n if (value === undefined || value === null) return undefined\n if (!isRecord(value) || Object.values(value).some((v) => typeof v !== 'string')) {\n throw new Error(`${name} must be an object mapping string keys to string values when provided`)\n }\n return value as Record<string, string>\n}\n\nfunction requireEnum<T extends string>(args: Record<string, unknown>, name: string, values: readonly T[]): T {\n const value = args[name]\n if (typeof value !== 'string' || !(values as readonly string[]).includes(value)) {\n throw new Error(`${name} must be one of: ${values.join(', ')}`)\n }\n return value as T\n}\n\nfunction optionalEnum<T extends string>(args: Record<string, unknown>, name: string, values: readonly T[]): T | undefined {\n const value = args[name]\n if (value === undefined || value === null) return undefined\n return requireEnum(args, name, values)\n}\n\n// ---------------------------------------------------------------------------\n// Shared helpers\n// ---------------------------------------------------------------------------\n\n/** Pick the first page id when the caller omits page_id. */\nfunction resolvePageId(document: SceneDocument, args: Record<string, unknown>): string {\n const pageId = optionalString(args, 'page_id')\n if (pageId) {\n requirePage(document, pageId)\n return pageId\n }\n const first = document.pages[0]\n if (!first) throw new Error('document has no pages')\n return first.id\n}\n\nfunction elementAabbRecord(el: SceneElement) {\n const aabb = elementAabb(el)\n return { x: aabb.x, y: aabb.y, width: aabb.width, height: aabb.height }\n}\n\nfunction pageSnapshot(page: ScenePage) {\n return {\n id: page.id,\n name: page.name,\n width: page.width,\n height: page.height,\n background: page.background,\n bleed: page.bleed,\n element_count: page.elements.length,\n elements: page.elements.map((el) => ({\n id: el.id,\n kind: el.kind,\n name: el.name,\n aabb: elementAabbRecord(el),\n rotation: el.rotation,\n opacity: el.opacity,\n locked: el.locked,\n visible: el.visible,\n ...(el.slot ? { slot: el.slot } : {}),\n })),\n }\n}\n\nasync function applyPlan(\n store: SceneStore,\n mintId: () => string,\n summary: string,\n operations: SceneOperation[],\n): Promise<{ rev: number; operation_count: number }> {\n const plan: ScenePlan = { summary, operations }\n const { record } = await storeApplyScenePlan(store, plan, { actorKind: 'agent_edit', mintId })\n return { rev: record.rev, operation_count: operations.length }\n}\n\n/** Collect slot→elementId mapping for a SINGLE page (no cross-page slot walk,\n * so duplicate slot names across different pages don't cause an error). */\nfunction collectPageSlotAttrs(page: ScenePage): Map<string, { elementId: string; kind: SceneElement['kind'] }> {\n const slots = new Map<string, { elementId: string; kind: SceneElement['kind'] }>()\n const stack = [...page.elements]\n while (stack.length > 0) {\n const el = stack.pop()!\n if (el.slot) {\n if (slots.has(el.slot)) {\n throw new Error(`duplicate slot name \"${el.slot}\" on page ${page.id}`)\n }\n slots.set(el.slot, { elementId: el.id, kind: el.kind })\n }\n if (el.kind === 'group') stack.push(...el.children)\n }\n return slots\n}\n\n\n// ---------------------------------------------------------------------------\n// JSON Schema helpers\n// ---------------------------------------------------------------------------\n\nfunction objectSchema(properties: Record<string, unknown>, required: string[]): Record<string, unknown> {\n return { type: 'object', properties, required, additionalProperties: false }\n}\n\nconst pageIdProp = { type: 'string', description: 'Page id to target. Omit to target the first page.' }\nconst elementIdProp = { type: 'string', description: 'Element id to target.' }\nconst xProp = { type: 'number', description: 'X position in page coordinates (px).' }\nconst yProp = { type: 'number', description: 'Y position in page coordinates (px).' }\nconst widthProp = { type: 'number', description: 'Width in px (must be > 0).' }\nconst heightProp = { type: 'number', description: 'Height in px (must be > 0).' }\nconst colorProp = (label: string) => ({ type: 'string', description: `${label} — hex (#rrggbb), rgb(), rgba(), or \"transparent\".` })\n\n// ---------------------------------------------------------------------------\n// Tool implementations\n// ---------------------------------------------------------------------------\n\nconst CANVAS_MCP_TOOLS: McpToolDefinition<DesignCanvasMcpToolEnv>[] = [\n\n // ─── read ────────────────────────────────────────────────────────────────\n\n {\n name: 'get_scene_state',\n description:\n 'Read the full scene document: title, settings, all pages with dimensions and background, and every element with its axis-aligned bounding box (so you can reason about layout and overlap without rendering). Call this before editing to get real page and element ids.',\n inputSchema: objectSchema({}, []),\n async run(_args, env) {\n const { document, rev } = await env.store.getDocument()\n return {\n rev,\n title: document.title,\n schema_version: document.schemaVersion,\n settings: document.settings,\n pages: document.pages.map(pageSnapshot),\n }\n },\n },\n\n {\n name: 'describe_page',\n description:\n 'Read one page in detail: name, dimensions, background, bleed, guides, and every element with geometry, attributes, and slot bindings. Use this when you need attribute values (fill, font, src) the compact get_scene_state summary omits.',\n inputSchema: objectSchema(\n { page_id: pageIdProp },\n ['page_id'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const { document, rev } = await env.store.getDocument()\n const page = requirePage(document, pageId)\n return {\n rev,\n id: page.id,\n name: page.name,\n width: page.width,\n height: page.height,\n background: page.background,\n bleed: page.bleed,\n guides: page.guides,\n elements: page.elements.map((el) => ({ ...el, aabb: elementAabbRecord(el) })),\n }\n },\n },\n\n {\n name: 'list_decisions',\n description: 'List recent agent decisions recorded against this document. Useful to audit what the agent has already done this session.',\n inputSchema: objectSchema(\n { limit: { type: 'number', description: 'Max decisions to return (default 20, max 100).' } },\n [],\n ),\n async run(args, env) {\n const limitRaw = optionalNumber(args, 'limit') ?? 20\n const limit = Math.min(100, Math.max(1, Math.round(limitRaw)))\n const decisions = await env.store.listDecisions(limit)\n return { decisions }\n },\n },\n\n // ─── add convenience wrappers ─────────────────────────────────────────────\n\n {\n name: 'add_text',\n description:\n 'Place a text element on a page. The element id is minted server-side and returned. x/y are the top-left in page coordinates (px). width sets the wrap column; height derives from content at render time.',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n text: { type: 'string', description: 'Initial text content.' },\n x: xProp,\n y: yProp,\n width: widthProp,\n font_size: { type: 'number', description: 'Font size in px (must be > 0). Default 24.' },\n font_family: { type: 'string', description: 'CSS font family. Default \"Inter\".' },\n font_style: { type: 'string', enum: ['normal', 'bold', 'italic', 'bold italic'], description: 'Default \"normal\".' },\n fill: colorProp('Text color. Default \"#000000\".'),\n align: { type: 'string', enum: ['left', 'center', 'right'], description: 'Default \"left\".' },\n line_height: { type: 'number', description: 'Line height multiplier. Default 1.2.' },\n letter_spacing: { type: 'number', description: 'Letter spacing in px. Default 0.' },\n name: { type: 'string', description: 'Layer name. Defaults to the first 32 chars of text.' },\n slot: { type: 'string', description: 'Template slot name — allows apply_data to replace this text programmatically.' },\n },\n ['text', 'x', 'y', 'width'],\n ),\n async run(args, env) {\n const { document } = await env.store.getDocument()\n const pageId = resolvePageId(document, args)\n const id = env.mintId()\n const text = requireString(args, 'text')\n const element: TextElement = {\n id,\n kind: 'text',\n name: optionalString(args, 'name') ?? text.slice(0, 32),\n x: requireNumber(args, 'x'),\n y: requireNumber(args, 'y'),\n width: requireNumber(args, 'width'),\n text,\n fontFamily: optionalString(args, 'font_family') ?? 'Inter',\n fontSize: optionalNumber(args, 'font_size') ?? 24,\n fontStyle: optionalEnum(args, 'font_style', ['normal', 'bold', 'italic', 'bold italic'] as const) ?? 'normal',\n fill: optionalString(args, 'fill') ?? '#000000',\n align: optionalEnum(args, 'align', ['left', 'center', 'right'] as const) ?? 'left',\n lineHeight: optionalNumber(args, 'line_height') ?? 1.2,\n letterSpacing: optionalNumber(args, 'letter_spacing') ?? 0,\n rotation: 0,\n opacity: 1,\n locked: false,\n visible: true,\n ...(() => { const s = optionalString(args, 'slot'); return s ? { slot: s } : {} })(),\n }\n const result = await applyPlan(env.store, env.mintId, `add text \"${text.slice(0, 40)}\"`, [\n { type: 'add_element', pageId, element },\n ])\n return { element_id: id, ...result }\n },\n },\n\n {\n name: 'add_image',\n description:\n 'Place an image element on a page. src must be an http(s) URL or a rooted /api/ path — never a data: blob. The element id is minted server-side and returned.',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n src: { type: 'string', description: 'Image URL (https://...) or /api/ path.' },\n x: xProp,\n y: yProp,\n width: widthProp,\n height: heightProp,\n fit: { type: 'string', enum: ['fill', 'cover', 'contain'], description: 'How the source maps into the frame. Default \"cover\".' },\n name: { type: 'string', description: 'Layer name. Default \"Image\".' },\n slot: { type: 'string', description: 'Template slot name — allows apply_data to swap this image\\'s src.' },\n },\n ['src', 'x', 'y', 'width', 'height'],\n ),\n async run(args, env) {\n const { document } = await env.store.getDocument()\n const pageId = resolvePageId(document, args)\n const id = env.mintId()\n const element: ImageElement = {\n id,\n kind: 'image',\n name: optionalString(args, 'name') ?? 'Image',\n x: requireNumber(args, 'x'),\n y: requireNumber(args, 'y'),\n width: requireNumber(args, 'width'),\n height: requireNumber(args, 'height'),\n src: requireString(args, 'src'),\n fit: optionalEnum(args, 'fit', ['fill', 'cover', 'contain'] as const) ?? 'cover',\n rotation: 0,\n opacity: 1,\n locked: false,\n visible: true,\n ...(() => { const s = optionalString(args, 'slot'); return s ? { slot: s } : {} })(),\n }\n const result = await applyPlan(env.store, env.mintId, 'add image element', [\n { type: 'add_element', pageId, element },\n ])\n return { element_id: id, ...result }\n },\n },\n\n {\n name: 'add_shape',\n description:\n 'Place a geometric shape on a page. kind must be \"rect\", \"ellipse\", or \"line\". For line: provide points as a flat [x0,y0,x1,y1,...] array (relative to x,y); for rect/ellipse: provide width and height.',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n kind: { type: 'string', enum: ['rect', 'ellipse', 'line'], description: 'Shape type.' },\n x: xProp,\n y: yProp,\n width: { type: 'number', description: 'Width in px (required for rect and ellipse).' },\n height: { type: 'number', description: 'Height in px (required for rect and ellipse).' },\n fill: colorProp('Fill color (rect/ellipse). Default \"#cccccc\".'),\n stroke: colorProp('Stroke/line color.'),\n stroke_width: { type: 'number', description: 'Stroke width in px.' },\n corner_radius: { type: 'number', description: 'Corner radius in px (rect only).' },\n points: {\n type: 'array',\n items: { type: 'number' },\n description: 'Flat [x0,y0,x1,y1,...] array for lines, relative to (x,y). Required for kind=\"line\".',\n },\n name: { type: 'string', description: 'Layer name.' },\n },\n ['kind', 'x', 'y'],\n ),\n async run(args, env) {\n const { document } = await env.store.getDocument()\n const pageId = resolvePageId(document, args)\n const id = env.mintId()\n const kind = requireEnum(args, 'kind', ['rect', 'ellipse', 'line'] as const)\n const x = requireNumber(args, 'x')\n const y = requireNumber(args, 'y')\n let element: SceneElement\n if (kind === 'rect') {\n const rect: RectElement = {\n id, kind, x, y,\n name: optionalString(args, 'name') ?? 'Rectangle',\n width: requireNumber(args, 'width'),\n height: requireNumber(args, 'height'),\n fill: optionalString(args, 'fill') ?? '#cccccc',\n rotation: 0, opacity: 1, locked: false, visible: true,\n ...(optionalString(args, 'stroke') ? { stroke: optionalString(args, 'stroke') } : {}),\n ...(optionalNumber(args, 'stroke_width') !== undefined ? { strokeWidth: optionalNumber(args, 'stroke_width') } : {}),\n ...(optionalNumber(args, 'corner_radius') !== undefined ? { cornerRadius: optionalNumber(args, 'corner_radius') } : {}),\n }\n element = rect\n } else if (kind === 'ellipse') {\n const ellipse: EllipseElement = {\n id, kind, x, y,\n name: optionalString(args, 'name') ?? 'Ellipse',\n width: requireNumber(args, 'width'),\n height: requireNumber(args, 'height'),\n fill: optionalString(args, 'fill') ?? '#cccccc',\n rotation: 0, opacity: 1, locked: false, visible: true,\n ...(optionalString(args, 'stroke') ? { stroke: optionalString(args, 'stroke') } : {}),\n ...(optionalNumber(args, 'stroke_width') !== undefined ? { strokeWidth: optionalNumber(args, 'stroke_width') } : {}),\n }\n element = ellipse\n } else {\n const rawPoints = args['points']\n if (!Array.isArray(rawPoints) || rawPoints.length < 4 || rawPoints.length % 2 !== 0) {\n throw new Error('points is required for kind=\"line\" and must be a flat [x0,y0,...] array with ≥ 2 points')\n }\n const points = rawPoints as number[]\n const line: LineElement = {\n id, kind, x, y, points,\n name: optionalString(args, 'name') ?? 'Line',\n stroke: optionalString(args, 'stroke') ?? '#000000',\n strokeWidth: optionalNumber(args, 'stroke_width') ?? 2,\n rotation: 0, opacity: 1, locked: false, visible: true,\n }\n element = line\n }\n const result = await applyPlan(env.store, env.mintId, `add ${kind} shape`, [\n { type: 'add_element', pageId, element },\n ])\n return { element_id: id, ...result }\n },\n },\n\n {\n name: 'add_video',\n description:\n 'Place a video element on a page. Video renders and exports as its poster frame — motion belongs to the sequences surface. src must be an http(s) URL or /api/ path.',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n src: { type: 'string', description: 'Video URL (https://...) or /api/ path.' },\n x: xProp,\n y: yProp,\n width: widthProp,\n height: heightProp,\n poster_src: { type: 'string', description: 'Poster frame URL shown before render. Optional.' },\n name: { type: 'string', description: 'Layer name. Default \"Video\".' },\n },\n ['src', 'x', 'y', 'width', 'height'],\n ),\n async run(args, env) {\n const { document } = await env.store.getDocument()\n const pageId = resolvePageId(document, args)\n const id = env.mintId()\n const element: VideoElement = {\n id,\n kind: 'video',\n name: optionalString(args, 'name') ?? 'Video',\n x: requireNumber(args, 'x'),\n y: requireNumber(args, 'y'),\n width: requireNumber(args, 'width'),\n height: requireNumber(args, 'height'),\n src: requireString(args, 'src'),\n rotation: 0,\n opacity: 1,\n locked: false,\n visible: true,\n ...(() => { const s = optionalString(args, 'poster_src'); return s ? { posterSrc: s } : {} })(),\n }\n const result = await applyPlan(env.store, env.mintId, 'add video element', [\n { type: 'add_element', pageId, element },\n ])\n return { element_id: id, ...result }\n },\n },\n\n // ─── mutations ────────────────────────────────────────────────────────────\n\n {\n name: 'set_attrs',\n description:\n 'Set one or more attributes on an existing element. Only provide the attrs you want to change — omitted attributes are unchanged. Attempting to change kind or id is an error. For convenience transforms (x/y/width/height/rotation) prefer move_element, resize_element, or rotate_element.',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n element_id: elementIdProp,\n attrs: {\n type: 'object',\n description: 'Attribute patch — any subset of the element\\'s mutable fields.',\n additionalProperties: true,\n },\n },\n ['page_id', 'element_id', 'attrs'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const elementId = requireString(args, 'element_id')\n if (!isRecord(args['attrs'])) throw new Error('attrs must be an object')\n const attrs = args['attrs'] as Record<string, unknown>\n const result = await applyPlan(env.store, env.mintId, `set attrs on ${elementId}`, [\n { type: 'set_attrs', pageId, elementId, attrs: attrs as import('./operations').SceneAttrsPatch },\n ])\n return result\n },\n },\n\n {\n name: 'move_element',\n description: 'Move an element to a new (x, y) position in page coordinates. Sugar for set_attrs({x, y}).',\n inputSchema: objectSchema(\n { page_id: pageIdProp, element_id: elementIdProp, x: xProp, y: yProp },\n ['page_id', 'element_id', 'x', 'y'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const elementId = requireString(args, 'element_id')\n const x = requireNumber(args, 'x')\n const y = requireNumber(args, 'y')\n const result = await applyPlan(env.store, env.mintId, `move element ${elementId} to (${x}, ${y})`, [\n { type: 'set_attrs', pageId, elementId, attrs: { x, y } },\n ])\n return result\n },\n },\n\n {\n name: 'resize_element',\n description: 'Resize an element by setting its width and height. Sugar for set_attrs({width, height}). Not valid on lines (use set_attrs with points instead).',\n inputSchema: objectSchema(\n { page_id: pageIdProp, element_id: elementIdProp, width: widthProp, height: heightProp },\n ['page_id', 'element_id', 'width', 'height'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const elementId = requireString(args, 'element_id')\n const width = requireNumber(args, 'width')\n const height = requireNumber(args, 'height')\n const result = await applyPlan(env.store, env.mintId, `resize element ${elementId} to ${width}×${height}`, [\n { type: 'set_attrs', pageId, elementId, attrs: { width, height } },\n ])\n return result\n },\n },\n\n {\n name: 'rotate_element',\n description: 'Set an element\\'s rotation in degrees (clockwise, about the element\\'s top-left origin). Sugar for set_attrs({rotation}).',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n element_id: elementIdProp,\n degrees: { type: 'number', description: 'Rotation in degrees (clockwise, 0–360 or negative).' },\n },\n ['page_id', 'element_id', 'degrees'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const elementId = requireString(args, 'element_id')\n const rotation = requireNumber(args, 'degrees')\n const result = await applyPlan(env.store, env.mintId, `rotate element ${elementId} to ${rotation}°`, [\n { type: 'set_attrs', pageId, elementId, attrs: { rotation } },\n ])\n return result\n },\n },\n\n {\n name: 'reorder_element',\n description:\n 'Change an element\\'s z-order within its owner (page root or parent group). toIndex is 0-based within the owner\\'s element list: 0 = bottom, length-1 = top.',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n element_id: elementIdProp,\n to_index: { type: 'number', description: '0-based target z-index within the element\\'s owner.' },\n },\n ['page_id', 'element_id', 'to_index'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const elementId = requireString(args, 'element_id')\n const toIndex = optionalNonNegativeInteger(args, 'to_index') ?? 0\n const result = await applyPlan(env.store, env.mintId, `reorder element ${elementId} to index ${toIndex}`, [\n { type: 'reorder_element', pageId, elementId, toIndex },\n ])\n return result\n },\n },\n\n {\n name: 'delete_element',\n description: 'Permanently delete an element (and all its children if it is a group) from a page.',\n inputSchema: objectSchema(\n { page_id: pageIdProp, element_id: elementIdProp },\n ['page_id', 'element_id'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const elementId = requireString(args, 'element_id')\n const result = await applyPlan(env.store, env.mintId, `delete element ${elementId}`, [\n { type: 'delete_element', pageId, elementId },\n ])\n return result\n },\n },\n\n {\n name: 'group_elements',\n description:\n 'Group 2+ sibling elements into a new group. Elements must share the same owner (page root or the same parent group). The group\\'s origin is set to the minimum bounding box of the members; children are rebased to group-local coordinates.',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n element_ids: { type: 'array', items: { type: 'string' }, minItems: 2, description: '≥2 sibling element ids to group.' },\n name: { type: 'string', description: 'Layer name for the new group. Default \"Group\".' },\n },\n ['page_id', 'element_ids'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const elementIds = requireStringArray(args, 'element_ids')\n const groupId = env.mintId()\n const result = await applyPlan(env.store, env.mintId, `group ${elementIds.length} elements`, [\n { type: 'group_elements', pageId, elementIds, groupId, name: optionalString(args, 'name') },\n ])\n return { group_id: groupId, ...result }\n },\n },\n\n {\n name: 'ungroup_element',\n description: 'Dissolve a group, promoting its children to the group\\'s parent owner at page coordinates. The group element is removed.',\n inputSchema: objectSchema(\n { page_id: pageIdProp, group_id: { type: 'string', description: 'Id of the group element to ungroup.' } },\n ['page_id', 'group_id'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const groupId = requireString(args, 'group_id')\n const result = await applyPlan(env.store, env.mintId, `ungroup ${groupId}`, [\n { type: 'ungroup_element', pageId, groupId },\n ])\n return result\n },\n },\n\n // ─── page management ──────────────────────────────────────────────────────\n\n {\n name: 'add_page',\n description: 'Add a new blank page to the document. Returns the new page\\'s id.',\n inputSchema: objectSchema(\n {\n name: { type: 'string', description: 'Page name.' },\n width: { type: 'number', description: 'Width in px. Default 1080.' },\n height: { type: 'number', description: 'Height in px. Default 1080.' },\n background: colorProp('Page background. Default \"#ffffff\".'),\n index: { type: 'number', description: 'Position in the page list (0-based). Omit to append.' },\n },\n [],\n ),\n async run(args, env) {\n const pageId = env.mintId()\n const result = await applyPlan(env.store, env.mintId, 'add page', [\n {\n type: 'add_page',\n pageId,\n options: {\n name: optionalString(args, 'name'),\n width: optionalNumber(args, 'width'),\n height: optionalNumber(args, 'height'),\n background: optionalString(args, 'background'),\n },\n index: optionalNonNegativeInteger(args, 'index'),\n },\n ])\n return { page_id: pageId, ...result }\n },\n },\n\n {\n name: 'duplicate_page',\n description:\n 'Duplicate an existing page including all its elements. Element ids are re-minted server-side to avoid conflicts. Returns the new page\\'s id.',\n inputSchema: objectSchema(\n {\n source_page_id: { type: 'string', description: 'Page id to copy.' },\n },\n ['source_page_id'],\n ),\n async run(args, env) {\n const sourcePageId = requireString(args, 'source_page_id')\n const pageId = env.mintId()\n const result = await applyPlan(env.store, env.mintId, `duplicate page ${sourcePageId}`, [\n { type: 'duplicate_page', sourcePageId, pageId },\n ])\n return { page_id: pageId, ...result }\n },\n },\n\n {\n name: 'delete_page',\n description: 'Delete a page. Fails if the document has only one page.',\n inputSchema: objectSchema(\n { page_id: { type: 'string', description: 'Page id to delete.' } },\n ['page_id'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const result = await applyPlan(env.store, env.mintId, `delete page ${pageId}`, [\n { type: 'delete_page', pageId },\n ])\n return result\n },\n },\n\n {\n name: 'set_page_props',\n description: 'Update a page\\'s name, dimensions, background color, or bleed. Pass null for bleed to clear it. Omit fields you don\\'t want to change.',\n inputSchema: objectSchema(\n {\n page_id: { type: 'string', description: 'Page id to update.' },\n name: { type: 'string', description: 'New page name.' },\n width: { type: 'number', description: 'New width in px.' },\n height: { type: 'number', description: 'New height in px.' },\n background: colorProp('New background color.'),\n bleed: {\n type: 'object',\n description: 'Bleed extents in px drawn OUTSIDE the page trim edge. Pass null to clear bleed.',\n properties: {\n top: { type: 'number' },\n right: { type: 'number' },\n bottom: { type: 'number' },\n left: { type: 'number' },\n },\n required: ['top', 'right', 'bottom', 'left'],\n nullable: true,\n },\n },\n ['page_id'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n let bleed: { top: number; right: number; bottom: number; left: number } | null | undefined\n if ('bleed' in args) {\n if (args['bleed'] === null) {\n bleed = null\n } else if (isRecord(args['bleed'])) {\n const b = args['bleed']\n bleed = {\n top: requireNumber(b, 'top'),\n right: requireNumber(b, 'right'),\n bottom: requireNumber(b, 'bottom'),\n left: requireNumber(b, 'left'),\n }\n } else {\n throw new Error('bleed must be an object with top/right/bottom/left or null to clear')\n }\n }\n const result = await applyPlan(env.store, env.mintId, `set props on page ${pageId}`, [\n {\n type: 'set_page_props',\n pageId,\n name: optionalString(args, 'name'),\n width: optionalNumber(args, 'width'),\n height: optionalNumber(args, 'height'),\n background: optionalString(args, 'background'),\n ...(bleed !== undefined ? { bleed } : {}),\n },\n ])\n return result\n },\n },\n\n {\n name: 'set_page_guides',\n description: 'Set ruler guides on a page. Replaces ALL existing guides for that axis — pass the full updated arrays.',\n inputSchema: objectSchema(\n {\n page_id: { type: 'string', description: 'Page id to update.' },\n vertical: { type: 'array', items: { type: 'number' }, description: 'Vertical guide positions in page-coordinate px.' },\n horizontal: { type: 'array', items: { type: 'number' }, description: 'Horizontal guide positions in page-coordinate px.' },\n },\n ['page_id', 'vertical', 'horizontal'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n if (!Array.isArray(args['vertical'])) throw new Error('vertical must be an array of numbers')\n if (!Array.isArray(args['horizontal'])) throw new Error('horizontal must be an array of numbers')\n const vertical = args['vertical'] as number[]\n const horizontal = args['horizontal'] as number[]\n const result = await applyPlan(env.store, env.mintId, `set guides on page ${pageId}`, [\n { type: 'set_page_guides', pageId, guides: { vertical, horizontal } },\n ])\n return result\n },\n },\n\n // ─── template / data binding ──────────────────────────────────────────────\n\n {\n name: 'bind_slot',\n description:\n 'Bind or unbind a template slot name to an element. Slot names are unique document-wide. Text elements accept string data; image/video elements accept src URLs. Pass null for slot to unbind.',\n inputSchema: objectSchema(\n {\n page_id: pageIdProp,\n element_id: elementIdProp,\n slot: { type: ['string', 'null'], description: 'Slot name to bind, or null to unbind.' },\n },\n ['page_id', 'element_id', 'slot'],\n ),\n async run(args, env) {\n const pageId = requireString(args, 'page_id')\n const elementId = requireString(args, 'element_id')\n const slot = args['slot'] === null ? null : requireString(args, 'slot')\n const result = await applyPlan(env.store, env.mintId, `bind slot \"${slot}\" to ${elementId}`, [\n { type: 'bind_slot', pageId, elementId, slot },\n ])\n return result\n },\n },\n\n {\n name: 'apply_data',\n description:\n 'Fill template slots with data. bindings is a {slot_name: value} map. Text slots accept any string; image/video slots accept http(s) URLs or /api/ paths; unknown slot names throw. Partial application is allowed.',\n inputSchema: objectSchema(\n {\n bindings: {\n type: 'object',\n description: 'Map of slot name → value. Text slots: string. Image/video slots: URL.',\n additionalProperties: { type: 'string' },\n },\n },\n ['bindings'],\n ),\n async run(args, env) {\n if (!isRecord(args['bindings'])) throw new Error('bindings must be an object')\n const bindings = args['bindings'] as Record<string, string>\n const result = await applyPlan(env.store, env.mintId, `apply data to ${Object.keys(bindings).length} slots`, [\n { type: 'apply_data', bindings },\n ])\n return result\n },\n },\n\n {\n name: 'instantiate_template',\n description:\n 'Clone this document\\'s page(s) with freshly minted element ids and fill the declared slots with the provided data bindings in one atomic call. Use this to produce personalized copies of a template document without modifying the original. Returns the new page ids in order. Note: this mutates the SAME document by appending copies of the requested pages with re-minted ids and applying data — it does NOT create a separate document. After calling, the document contains both the original pages and the new copies.',\n inputSchema: objectSchema(\n {\n source_page_ids: {\n type: 'array',\n items: { type: 'string' },\n description: 'Page ids to clone. Omit to clone all pages.',\n },\n bindings: {\n type: 'object',\n description: 'Slot name → value map applied after cloning.',\n additionalProperties: { type: 'string' },\n },\n },\n [],\n ),\n async run(args, env) {\n const { document } = await env.store.getDocument()\n const rawSourceIds = Array.isArray(args['source_page_ids'])\n ? (args['source_page_ids'] as string[])\n : document.pages.map((p) => p.id)\n const bindings = optionalRecord(args, 'bindings') ?? {}\n if (rawSourceIds.length === 0) throw new Error('source_page_ids must not be empty')\n\n // Phase 1: for each source page, collect any slots to temporarily unbind\n // from the source before duplicating. apply_data's collectSlots() walks ALL\n // pages, so it throws on duplicate slot names when source and copy share the\n // same slot attribute. The strategy: unbind source slots → duplicate →\n // apply_data (only copy pages carry slots now) → re-bind source slots. All\n // four phases are one atomic applyPlan call.\n const unbindOps: SceneOperation[] = []\n const rebindOps: SceneOperation[] = []\n const dupOps: SceneOperation[] = []\n const newPageIds: string[] = []\n\n for (const sourcePageId of rawSourceIds) {\n const sourcePage = requirePage(document, sourcePageId)\n // Collect slots on the source page (single-page walk, no collision check).\n const sourceSlots = collectPageSlotAttrs(sourcePage)\n for (const [slotName, { elementId }] of sourceSlots) {\n unbindOps.push({ type: 'bind_slot', pageId: sourcePageId, elementId, slot: null })\n rebindOps.push({ type: 'bind_slot', pageId: sourcePageId, elementId, slot: slotName })\n }\n const pageId = env.mintId()\n newPageIds.push(pageId)\n dupOps.push({ type: 'duplicate_page', sourcePageId, pageId })\n }\n\n const applyDataOps: SceneOperation[] = Object.keys(bindings).length > 0\n ? [{ type: 'apply_data', bindings }]\n : []\n\n // Op ordering is critical: duplicate FIRST (copies inherit source slots),\n // then unbind source slots (sources now have no slots, copies still do),\n // then apply_data (only copy pages carry slots — no duplicate names),\n // then re-bind source slots (restore original state of source pages).\n const allOps: SceneOperation[] = [\n ...dupOps,\n ...unbindOps,\n ...applyDataOps,\n ...rebindOps,\n ]\n\n const result = await applyPlan(\n env.store,\n env.mintId,\n `instantiate template (${rawSourceIds.length} pages)`,\n allOps,\n )\n return { new_page_ids: newPageIds, ...result }\n },\n },\n\n // ─── exports ──────────────────────────────────────────────────────────────\n\n {\n name: 'create_export',\n description:\n 'Queue a document export. For \"png\" and \"jpeg\", a render job is queued and status starts as \"queued\" — the host renders the Konva canvas and uploads the result. For \"json\", the export completes immediately with the full document JSON in metadata. Returns the export record id and initial status.',\n inputSchema: objectSchema(\n {\n format: { type: 'string', enum: ['png', 'jpeg', 'json'], description: 'Export format.' },\n page_id: { type: 'string', description: 'Page to export. Omit to export all pages (first page for images).' },\n pixel_ratio: { type: 'number', description: 'Device pixel ratio for raster exports. Default 1.' },\n },\n ['format'],\n ),\n async run(args, env) {\n const format = requireEnum(args, 'format', ['png', 'jpeg', 'json'] as const)\n const pageId = optionalString(args, 'page_id')\n const pixelRatio = optionalNumber(args, 'pixel_ratio') ?? 1\n\n let metadata: Record<string, unknown> = {\n pageId: pageId ?? null,\n pixelRatio,\n }\n\n if (format === 'json') {\n const { document, rev } = await env.store.getDocument()\n metadata = { ...metadata, document, rev }\n }\n\n const exportRecord = await env.store.createExport(format, metadata)\n await env.store.recordDecision({\n kind: 'export',\n instruction: `create ${format} export${pageId ? ` for page ${pageId}` : ''}`,\n metadata: { tool: 'create_export', export_id: exportRecord.id, format },\n })\n return {\n export_id: exportRecord.id,\n format: exportRecord.format,\n status: exportRecord.status,\n metadata: exportRecord.metadata,\n created_at: exportRecord.createdAt,\n }\n },\n },\n]\n\nexport { CANVAS_MCP_TOOLS }\n\nexport function findCanvasMcpTool(\n name: string,\n): McpToolDefinition<DesignCanvasMcpToolEnv> | undefined {\n return CANVAS_MCP_TOOLS.find((tool) => tool.name === name)\n}\n\nexport const CANVAS_MCP_TOOL_NAMES = CANVAS_MCP_TOOLS.map((t) => t.name)\nexport const CANVAS_ELEMENT_KINDS: readonly string[] = SCENE_ELEMENT_KINDS\n","/**\n * Streamable-HTTP MCP server for one design-canvas document — the live\n * agent→canvas channel. JSON-RPC 2.0 over POST, stateless per request\n * (Workers-compatible: no session table, no SSE).\n *\n * Trust boundary: the product authenticates the request (capability token,\n * workspace RBAC) BEFORE constructing the scoped {@link SceneStore} and\n * calling this handler — the handler trusts its store completely. Tool\n * execution failures (argument shape, validation, stale rev) become `isError`\n * tool results carrying the thrown message verbatim so the model can read WHY\n * and retry.\n *\n * The JSON-RPC envelope (initialize/ping/tools/list/tools/call, -32601) lives\n * in {@link createMcpToolHandler}; this module wires the canvas tool list +\n * the caller-supplied id-minting function.\n */\n\nimport type { SceneStore } from './store'\nimport { CANVAS_MCP_TOOLS } from './mcp-tools'\nimport type { DesignCanvasMcpToolEnv } from './mcp-tools'\nimport { createMcpToolHandler } from '../tools/mcp-rpc'\n\nexport interface DesignCanvasMcpServerInfo {\n name: string\n version: string\n}\n\nexport interface CreateDesignCanvasMcpHandlerOptions {\n /** Already scoped + authorized for one (workspace, document, actor). */\n store: SceneStore\n /** Id-minting function threaded into every mutating tool. Must return\n * a string unique within the document scope — use crypto.randomUUID()\n * in production; a deterministic counter in tests. */\n mintId: () => string\n serverInfo?: DesignCanvasMcpServerInfo\n}\n\nexport function createDesignCanvasMcpHandler(\n opts: CreateDesignCanvasMcpHandlerOptions,\n): (request: Request) => Promise<Response> {\n const serverInfo = opts.serverInfo ?? { name: 'design-canvas', version: '1.0.0' }\n\n return createMcpToolHandler<DesignCanvasMcpToolEnv>({\n serverInfo,\n tools: CANVAS_MCP_TOOLS,\n buildEnv: (_request) => ({ store: opts.store, mintId: opts.mintId }),\n })\n}\n","/**\n * Profile entry for the design-canvas MCP server — what a product spreads\n * into its sandbox `AgentProfile.mcp` map so the in-sandbox agent gets the\n * live canvas channel. A thin wrapper over the shared `buildScopedMcpServerEntry`\n * mechanism (../tools/mcp): transport 'http', capability token in the\n * Authorization header (server-set, never a tool argument). The only\n * domain-specific values are this channel's default description and name.\n */\n\nimport { buildScopedMcpServerEntry } from '../tools/mcp'\nimport type { AppToolMcpServer, ScopedMcpServerEntryOptions } from '../tools/mcp'\n\nexport const DEFAULT_DESIGN_CANVAS_MCP_DESCRIPTION =\n 'Live visual asset editor for the current design document: read scene state, add/move/resize/delete elements, manage pages, bind template slots, apply data, and queue exports. All coordinates are CSS pixels.'\n\nexport type BuildDesignCanvasMcpServerEntryOptions = ScopedMcpServerEntryOptions\n\n/** Build the `AgentProfileMcpServer`-shaped entry for the design-canvas channel. */\nexport function buildDesignCanvasMcpServerEntry(\n opts: BuildDesignCanvasMcpServerEntryOptions,\n): AppToolMcpServer {\n return buildScopedMcpServerEntry({\n ...opts,\n label: 'buildDesignCanvasMcpServerEntry',\n defaultDescription: DEFAULT_DESIGN_CANVAS_MCP_DESCRIPTION,\n })\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/interactions/contract.ts"],"sourcesContent":["// Shared contract for agent interaction events (kind: \"question\" et al) — the\n// generalized human-in-the-loop primitive on the chat stream. Framework-agnostic\n// (no server- or React-only imports) so a producer (chat route) and a consumer\n// (stream parser, transcript, question card) agree on one wire shape and one\n// persisted-part shape.\n//\n// An `interaction` event means the run is BLOCKED inside the sidecar's\n// InteractionBroker until the user answers, the agent withdraws the ask\n// (`interaction.cancel`), or the broker times out. A pending interaction is\n// \"waiting on the user\", not \"model working\".\n\nimport {\n InteractionRequestSchema,\n type InteractionData,\n type InteractionField,\n type InteractionOutcome,\n type InteractionRequest,\n} from '@tangle-network/agent-interface'\n\nexport type { InteractionData, InteractionOutcome, InteractionRequest }\n\n// ---------------------------------------------------------------------------\n// Event names\n\n/** Sidecar → client: the agent raised an ask; data = `{ request }`. */\nexport const INTERACTION_EVENT = 'interaction' as const\n/** Sidecar → client: the ask was withdrawn; data = `{ id, reason? }`. */\nexport const INTERACTION_CANCEL_EVENT = 'interaction.cancel' as const\n/** An ask was answered; data = `{ id, status }`. In the wire contract so a\n * server broadcast and a client-local mark share one event name. */\nexport const INTERACTION_RESOLVED_EVENT = 'interaction.resolved' as const\n\n/** Interaction kinds a product typically renders a card for. Anything else is\n * auto-declined by the chat producer's safety net and never reaches a client.\n * A pure default; a product may substitute its own renderable set. */\nconst RENDERABLE_INTERACTION_KINDS: ReadonlySet<string> = new Set(['question', 'plan'])\n\nexport function isRenderableInteractionKind(kind: string): boolean {\n return RENDERABLE_INTERACTION_KINDS.has(kind)\n}\n\n/** Answer/field keys the sidecar will accept: identifier-safe and never a\n * prototype-pollution vector. */\nexport function isSafeInteractionFieldKey(key: string): boolean {\n return /^[A-Za-z0-9_-]+$/.test(key) && key !== '__proto__' && key !== 'constructor' && key !== 'prototype'\n}\n\n// ---------------------------------------------------------------------------\n// Field types\n//\n// `allowCustom` (a select that also accepts a write-in value) is defined by\n// newer agent-interface schemas; older pinned schemas strip unknown keys on\n// parse. The wire/persisted field types below carry the flag so a card can gate\n// its write-in input, and `parseInteractionRequest` returns the RAW payload\n// (schema-validated, not schema-parsed) so the flag survives.\n\nexport type ChatSelectField = Extract<InteractionField, { type: 'select' }> & {\n allowCustom?: boolean\n}\nexport type ChatInteractionField = Exclude<InteractionField, { type: 'select' }> | ChatSelectField\n\n/** `InteractionRequest` whose select fields may carry `allowCustom`. */\nexport type InteractionRequestWire = Omit<InteractionRequest, 'answerSpec'> & {\n answerSpec: { fields: ChatInteractionField[] }\n}\n\n// ---------------------------------------------------------------------------\n// Interaction lifecycle\n\nexport type ChatInteractionStatus = 'pending' | 'answered' | 'declined' | 'cancelled' | 'expired'\n\n/** Accepted field selections keyed by answer-spec field name. */\n/** Accepted answer values. Select fields historically used `string[]`; the\n * wider scalar union is additive and lets durable stores preserve text,\n * numeric, and boolean answer fields without coercion. */\nexport type InteractionAnswerValue = string | number | boolean | string[]\nexport type InteractionAnswers = Record<string, InteractionAnswerValue>\n\nexport type ParseInteractionAnswersResult =\n | { succeeded: true; value: InteractionAnswers }\n | { succeeded: false; error: string }\n\n/** Strictly validates and copies persisted answer selections. */\nexport function parseInteractionAnswers(value: unknown): ParseInteractionAnswersResult {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return { succeeded: false, error: 'interaction answers must be an object' }\n }\n const answers: InteractionAnswers = {}\n for (const [key, selection] of Object.entries(value as Record<string, unknown>)) {\n if (!isSafeInteractionFieldKey(key)) {\n return { succeeded: false, error: `interaction answers contain an unsafe field key: ${key}` }\n }\n const valid = typeof selection === 'string' || typeof selection === 'number' || typeof selection === 'boolean' ||\n (Array.isArray(selection) && selection.every((item) => typeof item === 'string'))\n if (!valid) {\n return { succeeded: false, error: `interaction answer ${key} must be a string, number, boolean, or string array` }\n }\n answers[key] = Array.isArray(selection) ? [...selection] : selection\n }\n return { succeeded: true, value: answers }\n}\n\n/** The client/persisted view of one ask. `fields` come verbatim off the wire. */\nexport interface ChatInteraction {\n id: string\n kind: string\n title: string\n body?: string\n fields: ChatInteractionField[]\n status: ChatInteractionStatus\n /** Accepted selections, restored with the transcript after reload. */\n answers?: InteractionAnswers\n /** Set when status came from an `interaction.cancel` (e.g. \"timeout\"). */\n cancelReason?: string\n}\n\nexport function isTerminalInteractionStatus(status: ChatInteractionStatus): boolean {\n return status !== 'pending'\n}\n\n/** Statuses only move forward (pending → terminal); a replayed/stale `pending`\n * must never resurrect a resolved card. */\nexport function canTransitionInteractionStatus(\n from: ChatInteractionStatus,\n to: ChatInteractionStatus,\n): boolean {\n return from === 'pending' && to !== from\n}\n\n/** Maps an `interaction.cancel` reason to the card's terminal status. */\nexport function cancelStatusFor(reason: string | undefined): ChatInteractionStatus {\n return reason === 'timeout' ? 'expired' : 'cancelled'\n}\n\nfunction stableValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(stableValue)\n if (!value || typeof value !== 'object') return value\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nested]) => [key, stableValue(nested)]),\n )\n}\n\nfunction normalizedInteractionText(value: string | undefined): string {\n return (value ?? '').replace(/\\s+/g, ' ').trim()\n}\n\n/** Content identity for duplicate safety nets. Excludes volatile ids/statuses. */\nexport function questionInteractionContentSignature(interaction: ChatInteraction): string | null {\n if (interaction.kind !== 'question') return null\n return JSON.stringify(stableValue({\n kind: interaction.kind,\n title: normalizedInteractionText(interaction.title),\n body: normalizedInteractionText(interaction.body),\n fields: interaction.fields,\n }))\n}\n\nexport function dedupeQuestionInteractionsByContent(interactions: ChatInteraction[]): ChatInteraction[] {\n const seen = new Set<string>()\n return interactions.filter((interaction) => {\n const signature = questionInteractionContentSignature(interaction)\n if (!signature) return true\n if (seen.has(signature)) return false\n seen.add(signature)\n return true\n })\n}\n\n// ---------------------------------------------------------------------------\n// Wire parsing — typed outcomes, fail loud at the caller (log + skip; never a\n// half-rendered card).\n\nexport type ParseInteractionResult =\n | { succeeded: true; value: InteractionRequestWire }\n | { succeeded: false; error: string }\n\n/** Parses an `interaction` event's data (`{ request }`). Validates the shape\n * with the agent-interface schema but returns the raw request so a field a\n * pinned schema predates (`allowCustom`) survives. */\nexport function parseInteractionRequest(data: Record<string, unknown> | undefined): ParseInteractionResult {\n const request = data?.request\n if (!request || typeof request !== 'object') {\n return { succeeded: false, error: 'interaction event carried no request object' }\n }\n const validation = InteractionRequestSchema.safeParse(request)\n if (!validation.success) {\n return { succeeded: false, error: `malformed interaction request: ${validation.error.message}` }\n }\n return { succeeded: true, value: request as InteractionRequestWire }\n}\n\nexport interface InteractionCancelData {\n id: string\n reason?: string\n}\n\nexport function parseInteractionCancel(\n data: Record<string, unknown> | undefined,\n): { succeeded: true; value: InteractionCancelData } | { succeeded: false; error: string } {\n const id = typeof data?.id === 'string' && data.id ? data.id : null\n if (!id) return { succeeded: false, error: 'interaction.cancel event carried no id' }\n const reason = typeof data?.reason === 'string' && data.reason ? data.reason : undefined\n return { succeeded: true, value: { id, ...(reason ? { reason } : {}) } }\n}\n\n// ---------------------------------------------------------------------------\n// Composer-as-answer delivery: while asks are pending the composer never\n// blocks — typed text is delivered verbatim to every open ask and the agent\n// decides what it means. No interpretation here; the sidecar validates answers\n// fail-closed (invalid free text on an option-only ask → 400).\n\nexport function fieldAcceptsFreeText(field: ChatInteractionField): boolean {\n if (field.type === 'text') return true\n if (field.type === 'select') return (field as ChatSelectField).allowCustom === true\n return false\n}\n\nexport interface ComposerAnswerDelivery {\n interactionId: string\n field: ChatInteractionField\n}\n\n/** One delivery per pending ask: the first free-text-capable field, else the\n * first field. Zero-field asks are skipped (nothing to carry the text). */\nexport function composerAnswerDeliveries(pending: ChatInteraction[]): ComposerAnswerDelivery[] {\n const deliveries: ComposerAnswerDelivery[] = []\n for (const interaction of pending) {\n // Only questions take a composer-routed answer. A non-question ask (a plan)\n // is POSTed as outcome:\"accepted\" when answered — routing composer text to\n // it would silently APPROVE it. Approval is an explicit card click, so the\n // composer skips it and the plan card stays the only path.\n if (interaction.kind !== 'question') continue\n const field = interaction.fields.find(fieldAcceptsFreeText) ?? interaction.fields[0]\n if (!field) continue\n deliveries.push({ interactionId: interaction.id, field })\n }\n return deliveries\n}\n\n/** Shapes composer text into the respond payload for the routed field\n * (select answers are string arrays on the wire; text answers are strings). */\nexport function composerAnswerData(field: ChatInteractionField, text: string): InteractionData {\n return { [field.name]: field.type === 'select' ? [text] : text }\n}\n\n// ---------------------------------------------------------------------------\n// Part keys + codecs (persisted `messages.parts` entries and live stream parts\n// share these shapes).\n\nexport function interactionPartKey(id: string): string {\n return `interaction:${id}`\n}\n\nexport function noticePartKey(id: string): string {\n return `notice:${id}`\n}\n\nexport type NoticeKind = 'warning' | 'auto-declined'\n\n/**\n * Persisted-part shapes the codecs below produce — the SAME rows\n * `/chat-store`'s `ChatInteractionPart`/`ChatNoticePart` store, typed at the\n * source so a product pushing them into a `ChatMessagePart[]` transcript needs\n * no cast. Type aliases (not interfaces) on purpose: the implicit index\n * signature keeps them assignable to the `Record<string, unknown>` these\n * codecs previously returned, so existing consumers stay source-compatible.\n */\nexport type InteractionPersistedPart = {\n type: 'interaction'\n id: string\n kind: string\n title: string\n body?: string\n answerSpec: { fields: ChatInteractionField[] }\n status: ChatInteractionStatus\n answers?: InteractionAnswers\n cancelReason?: string\n}\n\nexport type NoticePersistedPart = {\n type: 'notice'\n id: string\n noticeKind: NoticeKind\n text: string\n}\n\n/** Builds the persisted/streamed `notice` part — a one-line transcript notice\n * explaining an out-of-band event (warning, auto-declined interaction). */\nexport function noticePart(noticeKind: NoticeKind, id: string, text: string): NoticePersistedPart {\n return { type: 'notice', id, noticeKind, text }\n}\n\n/** Reads a wire request into the client's pending `ChatInteraction`. */\nexport function interactionFromWireRequest(request: InteractionRequestWire): ChatInteraction {\n return {\n id: request.id,\n kind: request.kind,\n title: request.title,\n ...(request.body ? { body: request.body } : {}),\n fields: request.answerSpec.fields,\n status: 'pending',\n }\n}\n\n/** Builds the persisted/streamed `interaction` part from a wire request. */\nexport function interactionToPersistedPart(\n request: InteractionRequestWire,\n status: ChatInteractionStatus,\n cancelReason?: string,\n answers?: InteractionAnswers,\n): InteractionPersistedPart {\n const parsedAnswers = answers === undefined ? undefined : parseInteractionAnswers(answers)\n if (parsedAnswers && !parsedAnswers.succeeded) throw new TypeError(parsedAnswers.error)\n return {\n type: 'interaction',\n id: request.id,\n kind: request.kind,\n title: request.title,\n ...(request.body ? { body: request.body } : {}),\n answerSpec: { fields: request.answerSpec.fields },\n status,\n ...(parsedAnswers?.succeeded ? { answers: parsedAnswers.value } : {}),\n ...(cancelReason ? { cancelReason } : {}),\n }\n}\n\n/** Stamps accepted values onto matching persisted interaction parts without\n * mutating the caller's transcript or answer maps. */\nexport function stampInteractionAnswers(\n parts: Array<Record<string, unknown>>,\n answersByInteractionId: Readonly<Record<string, InteractionAnswers>>,\n): Array<Record<string, unknown>> {\n return parts.map((part) => {\n if (String(part.type ?? '') !== 'interaction' || typeof part.id !== 'string') return part\n if (!Object.prototype.hasOwnProperty.call(answersByInteractionId, part.id)) return part\n const rawAnswers = answersByInteractionId[part.id]\n if (rawAnswers === undefined) return part\n const parsed = parseInteractionAnswers(rawAnswers)\n if (!parsed.succeeded) throw new TypeError(parsed.error)\n return { ...part, answers: parsed.value }\n })\n}\n\n/** Reads a persisted/streamed `interaction` part back into a `ChatInteraction`.\n * Returns null (caller logs) when the part is not one of ours. */\nexport function persistedPartToInteraction(part: Record<string, unknown>): ChatInteraction | null {\n if (String(part.type ?? '') !== 'interaction') return null\n const id = typeof part.id === 'string' && part.id ? part.id : null\n const kind = typeof part.kind === 'string' && part.kind ? part.kind : null\n const title = typeof part.title === 'string' ? part.title : ''\n const answerSpec = part.answerSpec as { fields?: unknown } | undefined\n const fields = Array.isArray(answerSpec?.fields) ? (answerSpec.fields as ChatInteractionField[]) : null\n const status = part.status as ChatInteractionStatus | undefined\n const validStatus = status && ['pending', 'answered', 'declined', 'cancelled', 'expired'].includes(status)\n const parsedAnswers = part.answers === undefined ? undefined : parseInteractionAnswers(part.answers)\n if (!id || !kind || !fields || !validStatus || (parsedAnswers && !parsedAnswers.succeeded)) return null\n return {\n id,\n kind,\n title,\n ...(typeof part.body === 'string' && part.body ? { body: part.body } : {}),\n fields,\n status,\n ...(parsedAnswers?.succeeded ? { answers: parsedAnswers.value } : {}),\n ...(typeof part.cancelReason === 'string' && part.cancelReason ? { cancelReason: part.cancelReason } : {}),\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/chat-routes/wire.ts","../src/chat-store/parts.ts"],"sourcesContent":["/**\n * Wire contract between the chat client (composer + `streamChatTurn`) and the\n * assembled server vertical (`createChatTurnRoutes`). Import-free on purpose:\n * `/web-react` re-exports these types into browser bundles, so nothing here may\n * reach a Node builtin or an engine package.\n *\n * The part shape mirrors the sandbox SDK's `PromptInputPart` structurally\n * (text | image | file with filename/mediaType/url/path/content) — derived\n * here, not imported, so the client bundle never touches the SDK.\n */\n\nexport interface ChatTurnTextPartInput {\n type: 'text'\n text: string\n}\n\n/** A non-text prompt part the upload route hands back and the client echoes\n * on send. `url` carries an inline `data:` URI for small files; `path` is a\n * sandbox workspace reference for large ones (the >1 MiB gateway body cap\n * makes the two-step upload mandatory). */\nexport interface ChatTurnFilePartInput {\n type: 'image' | 'file'\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n content?: string\n}\n\nexport type ChatTurnPartInput = ChatTurnTextPartInput | ChatTurnFilePartInput\n\n// ── producer stream vocabulary ───────────────────────────────────────────────\n\nexport interface ProducerTextEvent {\n type: 'text'\n text: string\n}\n\nexport interface ProducerReasoningEvent {\n type: 'reasoning'\n text: string\n}\n\nexport interface ProducerToolCallEvent {\n type: 'tool_call'\n call: {\n toolCallId: string\n toolName: string\n args: Record<string, unknown>\n }\n}\n\nexport interface ProducerToolResultEvent {\n type: 'tool_result'\n toolCallId: string\n toolName: string\n outcome: {\n ok: boolean\n result?: unknown\n message?: string\n }\n}\n\nexport interface ProducerUsageEvent {\n type: 'usage'\n usage: {\n promptTokens: number\n completionTokens: number\n }\n}\n\nexport interface ProducerNoticeEvent {\n type: 'notice'\n id: string\n /** Kept inline with `/interactions`' `NoticeKind` so this file stays import-free. */\n noticeKind: 'warning' | 'auto-declined'\n text: string\n}\n\nexport interface ProducerErrorEvent {\n type: 'error'\n data: {\n message: string\n code?: string\n details?: Record<string, unknown>\n }\n}\n\n/** Stable raw lifecycle/interaction/plan/route events forwarded unchanged. */\nexport type ProducerPassthroughEventType =\n | 'turn'\n | 'metadata'\n | 'interaction'\n | 'interaction.cancel'\n | 'plan.submitted'\n | 'done'\n | 'warning'\n | 'session.run.started'\n | 'session.run.completed'\n | 'session.run.failed'\n | 'turn_status'\n\nexport interface ProducerPassthroughEvent {\n type: ProducerPassthroughEventType\n data?: Record<string, unknown>\n /** Route markers and raw passthroughs may carry `turnId`, `status`, `seq`, etc. */\n [key: string]: unknown\n}\n\nexport type ProducerWireEvent =\n | ProducerTextEvent\n | ProducerReasoningEvent\n | ProducerToolCallEvent\n | ProducerToolResultEvent\n | ProducerUsageEvent\n | ProducerNoticeEvent\n | ProducerErrorEvent\n | ProducerPassthroughEvent\n\n/** The image/file split an attachment is rendered and persisted under — the\n * same discriminant as {@link ChatMentionKind}, but a distinct name because an\n * attachment carries content the product uploaded (`ChatAttachmentInput`)\n * while a mention points at a file the box already has. Defined HERE (the\n * import-free layer) so `ChatAttachmentInput` can reference it and the client\n * composer imports it without pulling the persisted-part vocabulary;\n * `/chat-store`'s parts module re-exports it alongside the attachment helpers. */\nexport type ChatAttachmentKind = 'image' | 'file'\n\n/** `POST` turn-body entry describing a file already uploaded to the product's\n * store (vault/object-store) — distinct from an inline {@link\n * ChatTurnFilePartInput} (which carries bytes) and from a {@link FileMention}\n * (a sandbox path the box already holds). The route resolves this field with\n * {@link resolveChatAttachments}: every path is re-validated and every size is\n * re-derived from the stored body, so nothing here is trusted as sent. */\nexport interface ChatAttachmentInput {\n path: string\n name: string\n size: number\n mediaType: string\n kind: ChatAttachmentKind\n}\n\n/** POST body for the turn route. `content` may be empty when `parts` carry the\n * message (an image-only send). Product routing fields (workspaceId etc.) ride\n * alongside and are read by the product's `authorize` seam. */\nexport interface ChatTurnRequestPayload {\n threadId: string\n content?: string\n /** Non-text parts from the upload route, echoed back verbatim. */\n parts?: ChatTurnFilePartInput[]\n /** `@`-picked file mentions for this turn — path references into the\n * workspace sandbox, NOT uploads, so they travel in their own field rather\n * than as `parts` entries. A product whose `parts` field is already spoken\n * for (an attachment sentinel) can still send mentions, and mentions\n * persist as their own `ChatMentionPart`s so a retry rebuilds them. The\n * route validates this field with {@link parseFileMentions} and replaces it\n * on the payload with the validated, deduped list. */\n mentions?: FileMention[]\n /** Files uploaded to the product's store ahead of the turn — path\n * references, NOT inline bytes (those ride `parts`). Validated and\n * size-re-derived by {@link resolveChatAttachments} into persistable\n * attachment parts; a product whose `parts` field is spoken for by inline\n * uploads still sends store-backed files here. */\n attachments?: ChatAttachmentInput[]\n model?: string\n effort?: 'auto' | 'low' | 'medium' | 'high'\n harness?: string\n /** Client-generated idempotency key for the logical turn (retry-safe). */\n turnId?: string\n [key: string]: unknown\n}\n\n/** `fetch` init for the turn route — the one place the client wire shape is\n * serialized, so composer glue and products never drift from the server's\n * parser. */\nexport function chatTurnRequestInit(payload: ChatTurnRequestPayload): RequestInit {\n return {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n}\n\n// ── inline-part byte budget ─────────────────────────────────────────────────\n//\n// The sandbox gateway caps request bodies at 1 MiB; a turn body whose inline\n// `data:` parts exceed it dies at the gateway with an opaque 413. Enforce the\n// budget at the route boundary instead, with headroom for the JSON envelope\n// (same fail-loud-at-the-choke-point style as /sandbox's provision-payload and\n// env-size gates).\n\nexport const INLINE_PARTS_MAX_BYTES = 950_000\n\n// ── dispatch (parts[]) budget vocabulary ────────────────────────────────────\n//\n// The default caps `buildDispatchParts` sizes an attachment/mention dispatch\n// against — the sidecar/proxy limits an assembled `parts` array crosses, one\n// step past `INLINE_PARTS_MAX_BYTES` (which gates the raw turn BODY). Grouped\n// here, in the import-free layer, so the numbers are one overridable\n// vocabulary the client can read and a product can tune per call rather than\n// constants buried in the server module. NOTE: these model sidecar/proxy caps,\n// not a product's MIME accept-list or vault bucketing — those are DOMAIN\n// values the product supplies, never defaulted here.\n\n/** Hard cap on the whole `/prompt` request body as it crosses the sandbox\n * proxy — smaller in practice than a raw-file write cap because a dispatch\n * carries several inline parts plus the flattened history in one request. */\nexport const DISPATCH_REQUEST_MAX_BYTES = 1024 * 1024\n\n/** Bytes reserved off the top of {@link DISPATCH_REQUEST_MAX_BYTES} for the\n * JSON structure around the parts array (keys, delimiters, per-part\n * `type`/`filename`/`mediaType` fields) that {@link base64WireLen} does not\n * account for — keeps the inline budget off the exact proxy cap where one\n * stray byte trips the 413. */\nexport const DISPATCH_STRUCTURAL_RESERVE_BYTES = 64 * 1024\n\n/** Sidecar's hard cap on the `parts` array of one prompt request — a dispatch\n * must never assemble more parts than this or the whole turn 400s. */\nexport const DISPATCH_MAX_PARTS = 64\n\n/** Product-side cap on media parts per dispatch (current turn + carried\n * history), well under {@link DISPATCH_MAX_PARTS}. History trimming that keeps\n * a transcript's native media under this is a PRODUCT concern (the pointer\n * block keeps trimmed media reachable); `buildDispatchParts` enforces only the\n * total {@link DISPATCH_MAX_PARTS} cap. */\nexport const DISPATCH_MAX_MEDIA_PARTS = 24\n\n/** Size a base64-encoded string occupies on the wire given the raw\n * (pre-encoding) byte length: base64 packs 3 raw bytes into 4 output\n * characters, rounded up to the next multiple of 4. */\nexport function base64WireLen(byteLen: number): number {\n return Math.ceil(byteLen / 3) * 4\n}\n\n/**\n * Render a raw byte count as a human-readable size (`512B`, `3KB`, `12MB\n * 500KB`). Ported EXACTLY from gtm-agent's `attachment-limits.ts` — byte-\n * identical implementation, not a reinterpretation — so `resolve-attachments`'s\n * and `promote-file-part`'s error strings match gtm's wording verbatim. Lives\n * in the import-free wire layer (not `resolve-attachments.ts` alone) because\n * BOTH the aggregate-cap message here and the per-file oversize message in\n * `promote-file-part.ts` need it; a browser composer wanting the same\n * formatting for a client-side pre-check can also import it with no engine\n * pulled in.\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`\n if (bytes >= 1024 * 1024) {\n const megabytes = Math.floor(bytes / (1024 * 1024))\n const remainder = bytes % (1024 * 1024)\n return remainder === 0 ? `${megabytes}MB` : `${megabytes}MB ${formatBytes(remainder)}`\n }\n return `${Math.round(bytes / 1024)}KB`\n}\n\nexport class ChatTurnInputError extends Error {\n constructor(message: string, readonly status = 400, readonly code = 'INVALID_CHAT_TURN') {\n super(message)\n this.name = 'ChatTurnInputError'\n }\n}\n\nfunction partByteSize(part: ChatTurnPartInput): number {\n let bytes = 0\n if (part.type === 'text') return part.text.length\n if (part.url) bytes += part.url.length\n if (part.content) bytes += part.content.length\n if (part.path) bytes += part.path.length\n return bytes\n}\n\nexport function promptPartsByteSize(parts: ChatTurnPartInput[]): number {\n return parts.reduce((total, part) => total + partByteSize(part), 0)\n}\n\n/** Throws `ChatTurnInputError` (413) when the parts' inline payload would blow\n * the gateway cap. Path-ref parts are tiny by construction and always pass. */\nexport function assertPromptPartsWithinCap(\n parts: ChatTurnPartInput[],\n maxBytes = INLINE_PARTS_MAX_BYTES,\n): void {\n const total = promptPartsByteSize(parts)\n if (total <= maxBytes) return\n const largest = [...parts].sort((a, b) => partByteSize(b) - partByteSize(a))[0]\n const largestName = largest && largest.type !== 'text' ? largest.filename ?? largest.path ?? largest.type : 'text'\n throw new ChatTurnInputError(\n `Inline prompt parts total ${total}B, over the ${maxBytes}B budget (largest: ${largestName}, ${largest ? partByteSize(largest) : 0}B). ` +\n 'Upload large files through the upload route so they travel as sandbox path references.',\n 413,\n 'PROMPT_PARTS_TOO_LARGE',\n )\n}\n\n// ── file mentions ────────────────────────────────────────────────────────\n//\n// A file mention (`@`-picked in the composer, sandbox-ui#184) is a path\n// reference into the workspace sandbox — no byte upload. These helpers turn\n// a resolved mention list into wire parts and the prompt pointer block that\n// tells the agent where to read them from.\n\n/** A file mention resolved from the composer's `@`-picker: the\n * workspace-relative path plus enough metadata to build a prompt part and\n * pointer text. `path` is the canonical identity — the mention pill's\n * `MentionItem.id` for the file kind (`/web-react`'s `useFileMentions`). */\nexport interface FileMention {\n path: string\n name: string\n size?: number\n}\n\nconst MENTION_IMAGE_MEDIA_TYPES: ReadonlyMap<string, string> = new Map([\n ['.png', 'image/png'],\n ['.jpg', 'image/jpeg'],\n ['.jpeg', 'image/jpeg'],\n ['.gif', 'image/gif'],\n ['.webp', 'image/webp'],\n ['.svg', 'image/svg+xml'],\n ['.bmp', 'image/bmp'],\n ['.heic', 'image/heic'],\n ['.heif', 'image/heif'],\n ['.avif', 'image/avif'],\n])\n\nfunction extensionOf(path: string): string {\n const base = path.split('/').filter(Boolean).pop() ?? path\n const dot = base.lastIndexOf('.')\n return dot > 0 ? base.slice(dot).toLowerCase() : ''\n}\n\n/** The `image/*` mime for a mention path by extension, or `undefined` for\n * anything not in the known image set (dispatched as `type: 'file'`). */\nexport function mediaTypeForMentionPath(path: string): string | undefined {\n return MENTION_IMAGE_MEDIA_TYPES.get(extensionOf(path))\n}\n\n/** The image/file split a mention is rendered and persisted under — the\n * composer pill's icon, the dispatched part's `type`, and\n * `ChatMentionPart.mentionKind` are all this one value. */\nexport type ChatMentionKind = 'image' | 'file'\n\n/** `image` when the path's extension is in the known image set (the same table\n * {@link mediaTypeForMentionPath} reads), `file` otherwise. Exported so a\n * client that needs only the discriminant — a pill icon, a persisted part's\n * `mentionKind` — never re-declares the extension table; two frozen copies of\n * one mime table is how one gains a format and the other doesn't. */\nexport function mentionKindForPath(path: string): ChatMentionKind {\n return mediaTypeForMentionPath(path) ? 'image' : 'file'\n}\n\nexport interface FileMentionsToPartsOptions {\n /** Resolve a mention's workspace-relative path to the absolute path the\n * dispatched part should carry (e.g. a host prefixing the in-box vault\n * root). Default: identity — the path travels unchanged. */\n resolvePath?: (path: string) => string\n}\n\n/** Maps resolved file mentions to path-only `ChatTurnFilePartInput`s —\n * `image` vs `file` by extension, and always a `path`, never a `url` (the\n * url/path XOR invariant: a mention is a sandbox path reference, never\n * inline bytes). */\nexport function fileMentionsToParts(\n mentions: readonly FileMention[],\n opts: FileMentionsToPartsOptions = {},\n): ChatTurnFilePartInput[] {\n const resolvePath = opts.resolvePath ?? ((path: string) => path)\n return mentions.map((mention) => {\n const mediaType = mediaTypeForMentionPath(mention.path)\n const part: ChatTurnFilePartInput = {\n type: mediaType ? 'image' : 'file',\n filename: mention.name,\n path: resolvePath(mention.path),\n }\n if (mediaType) part.mediaType = mediaType\n return part\n })\n}\n\n/** The agent-facing pointer block appended to the dispatched prompt — never\n * persisted in message `content`. Empty array → `''` so callers can append\n * unconditionally. This is the sole producer of that text: the current\n * turn's dispatch and any history projection built from the same mention\n * list both route through here, so the two can't drift apart. */\nexport function buildMentionPromptBlock(\n mentions: readonly Pick<FileMention, 'name' | 'path'>[],\n): string {\n if (mentions.length === 0) return ''\n const lines = mentions.map((m) => `- ${m.name} (${m.path})`)\n return `\\n\\nMentioned files — read them from these paths:\\n${lines.join('\\n')}`\n}\n\n// ── mention validation ───────────────────────────────────────────────────\n//\n// This package owns BOTH ends of the mention path contract — it emits paths\n// from `createSandboxFileIndexRoute` and consumes them in `fileMentionsToParts`\n// / `buildMentionPromptBlock` — so the validation belongs here rather than in\n// each app that wires the pair up. A mention names a file that already exists\n// in the sandbox, so validation is a pure path/charset/count check with no\n// I/O: existence is proven later, when the agent reads the path and the turn\n// fails loudly if it is gone.\n\n/** Hard cap on mentions per turn. Bounds the prompt pointer block, the\n * persisted parts, and whatever media budget a dispatch draws from them. */\nexport const MENTION_MAX_COUNT = 16\n\n/** Longest mention display name accepted — bounds the pointer-block text and\n * the transcript pill label. */\nconst MAX_MENTION_NAME_LENGTH = 256\n/** Longest mention path accepted. */\nconst MAX_MENTION_PATH_LENGTH = 1024\n\nexport type SandboxMentionPathCheck =\n | { succeeded: true }\n | { succeeded: false; error: string }\n\n/**\n * Validate a workspace-relative sandbox mention path. Rejects traversal (a\n * `..` path segment), absolute paths (leading `/`), backslashes, and null\n * bytes — the four ways a path picked in a client can escape the root the\n * index route scanned.\n *\n * Spaces and unicode are deliberately ALLOWED: in-box filenames are arbitrary,\n * and an ASCII-only charset would silently drop real files from a feature\n * whose whole job is naming them.\n */\nexport function validateSandboxMentionPath(path: unknown): SandboxMentionPathCheck {\n if (typeof path !== 'string' || path.length === 0) {\n return { succeeded: false, error: 'mention path must be a non-empty string' }\n }\n if (path.length > MAX_MENTION_PATH_LENGTH) {\n return { succeeded: false, error: `mention path must not exceed ${MAX_MENTION_PATH_LENGTH} characters` }\n }\n if (path.includes('\\0')) {\n return { succeeded: false, error: 'mention path must not contain null bytes' }\n }\n if (path.includes('\\\\')) {\n return { succeeded: false, error: 'mention path must not contain backslashes' }\n }\n if (path.startsWith('/')) {\n return { succeeded: false, error: 'mention path must be workspace-relative, not absolute' }\n }\n if (path.split('/').some((segment) => segment === '..')) {\n return { succeeded: false, error: 'mention path must not contain \"..\" segments' }\n }\n return { succeeded: true }\n}\n\nfunction parseFileMention(value: unknown, index: number): FileMention {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new ChatTurnInputError(`mentions[${index}] must be an object`)\n }\n const record = value as Record<string, unknown>\n\n const pathCheck = validateSandboxMentionPath(record.path)\n if (!pathCheck.succeeded) throw new ChatTurnInputError(`mentions[${index}]: ${pathCheck.error}`)\n\n const name = record.name\n if (typeof name !== 'string' || !name.trim()) {\n throw new ChatTurnInputError(`mentions[${index}].name must be a non-empty string`)\n }\n if (name.length > MAX_MENTION_NAME_LENGTH) {\n throw new ChatTurnInputError(`mentions[${index}].name must not exceed ${MAX_MENTION_NAME_LENGTH} characters`)\n }\n\n const size = record.size\n if (size !== undefined) {\n if (typeof size !== 'number' || !Number.isFinite(size)) {\n throw new ChatTurnInputError(`mentions[${index}].size must be a finite number`)\n }\n if (size < 0) {\n throw new ChatTurnInputError(`mentions[${index}].size must not be negative`)\n }\n }\n\n return { path: record.path as string, name, ...(typeof size === 'number' ? { size } : {}) }\n}\n\n/**\n * Validates the untyped `mentions` array off the wire, mirroring\n * {@link parseChatTurnParts}: the typed list, or `ChatTurnInputError` (400)\n * naming the offending entry. Never sanitizes-and-continues — a traversal path\n * is a rejected request, not a trimmed one.\n *\n * A path repeated within one turn is deduped to its first occurrence rather\n * than rejected: mentioning the same file twice is plausible user input, not\n * an attack.\n */\nexport function parseFileMentions(raw: unknown): FileMention[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('mentions must be an array')\n if (raw.length > MENTION_MAX_COUNT) {\n throw new ChatTurnInputError(`mentions must not exceed ${MENTION_MAX_COUNT} entries`)\n }\n\n const mentions: FileMention[] = []\n const seenPaths = new Set<string>()\n for (let index = 0; index < raw.length; index += 1) {\n const mention = parseFileMention(raw[index], index)\n if (seenPaths.has(mention.path)) continue\n seenPaths.add(mention.path)\n mentions.push(mention)\n }\n return mentions\n}\n\n/** Validates the untyped `parts` array off the wire. Returns the typed parts\n * or throws `ChatTurnInputError` (400) naming the offending entry. */\nexport function parseChatTurnParts(raw: unknown): ChatTurnFilePartInput[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('parts must be an array')\n return raw.map((entry, index) => {\n const part = entry as Record<string, unknown> | null\n if (!part || typeof part !== 'object') {\n throw new ChatTurnInputError(`parts[${index}] must be an object`)\n }\n if (part.type !== 'image' && part.type !== 'file') {\n throw new ChatTurnInputError(`parts[${index}].type must be 'image' or 'file'`)\n }\n for (const key of ['filename', 'mediaType', 'url', 'path', 'content'] as const) {\n if (part[key] !== undefined && typeof part[key] !== 'string') {\n throw new ChatTurnInputError(`parts[${index}].${key} must be a string`)\n }\n }\n if (!part.url && !part.path && !part.content) {\n throw new ChatTurnInputError(`parts[${index}] needs a url, path, or content`)\n }\n return {\n type: part.type,\n ...(part.filename !== undefined ? { filename: part.filename as string } : {}),\n ...(part.mediaType !== undefined ? { mediaType: part.mediaType as string } : {}),\n ...(part.url !== undefined ? { url: part.url as string } : {}),\n ...(part.path !== undefined ? { path: part.path as string } : {}),\n ...(part.content !== undefined ? { content: part.content as string } : {}),\n }\n })\n}\n","/**\n * The stored shape of `message.parts` — one typed vocabulary for every part a\n * product persists into a chat transcript. NOT an ad-hoc union reverse-\n * engineered from product schemas; each member is matched field-for-field to\n * its canonical source:\n *\n * - `text` / `reasoning` / `tool`: the persisted projection `/stream`'s\n * `normalizePersistedPart` produces from the harness lane's\n * `message.part.updated` events (ADC sidecar\n * `apps/sidecar/src/events/session-events.ts:56` wraps the canonical part in\n * an `{id, sessionID, messageID}` envelope; the projection strips the\n * session/message ids and keeps the per-segment part id).\n * - `file` / `image` / `step-start` / `step-finish`: the sidecar's canonical\n * `MessagePartSchema` members (ADC\n * `apps/sidecar/src/schemas/agent-schemas.ts:50-154`); `step-finish` carries\n * the harness's per-step usage receipt — tokens\n * `{total, input, output, reasoning, cache{write, read}}` + `cost` — which is\n * also the shape the message-level token/cost columns mirror.\n * - `subtask`: `@tangle-network/agent-interface`'s `SubtaskPart` (a spawned\n * sub-agent task).\n * - `interaction` / `notice`: the persisted-part codecs in\n * `/web-react`'s chat-interactions contract (`interactionToPersistedPart`,\n * `noticePart`) — type-only imports, one source of truth for their statuses\n * and field shapes.\n * - `plan`: the durable-plan projection in `/plans`, derived from the sandbox\n * SDK's authoritative plan lifecycle.\n * - `mention`: an `@`-picked reference to a file that already lives in the\n * workspace sandbox (`FileMention` in `/chat-routes`'s wire contract, plus\n * the image/file discriminant). Neither transport lane produces it — the\n * turn route persists it from the request's `mentions` field — but it is a\n * part a product persists into a transcript, so it belongs in this\n * vocabulary rather than in a parallel one.\n *\n * `@tangle-network/agent-interface` exports the canonical wire `Part` union,\n * but its `PartBase` requires the `sessionID`/`messageID` stream envelope that\n * is deliberately NOT persisted, so the stored union is defined here as the\n * envelope-free projection (a type-level coverage check against the peer's\n * `Part['type']` lives in the tests). Contribute-down candidate: if\n * agent-interface grows envelope-free persisted-part types, re-export them\n * here and delete these definitions.\n *\n * Two transport lanes serialize into this SAME stored shape:\n * - harness lane: canonical `message.part.updated` parts, merged/normalized by\n * `/stream` (`mergePersistedPart`, `finalizeAssistantParts`);\n * - router/openai-compat lane: `text_delta`/`tool_call` stream events are\n * mapped INTO canonical part events first (`/runtime`'s `toLoopEvents` +\n * `/stream`'s `normalizeToolEvent`) and then persisted identically — the\n * store never sees a router-specific shape.\n */\n\nimport type { Part as HarnessWirePart } from '@tangle-network/agent-interface'\nimport type {\n ChatInteractionField,\n ChatInteractionStatus,\n InteractionAnswers,\n InteractionPersistedPart,\n NoticeKind,\n NoticePersistedPart,\n} from '../web-react/chat-interactions'\nimport { persistedPartToInteraction } from '../interactions/contract'\nimport {\n persistedPartToPlan,\n planToPersistedPart,\n type ChatPlanPersistedPart,\n} from '../plans/index'\n// `./wire` is import-free by construction, so this edge costs the store\n// nothing and keeps ONE image-extension table for the whole mention layer.\nimport {\n mentionKindForPath,\n type ChatAttachmentInput,\n type ChatAttachmentKind,\n type ChatMentionKind,\n type FileMention,\n} from '../chat-routes/wire'\n\nexport type { ChatMentionKind, ChatAttachmentKind }\n\n/** Start/end wall-clock millis, as normalized by `/stream`'s `normalizeTime`. */\nexport interface ChatPartTime {\n start?: number\n end?: number\n}\n\n/** `id` is the harness's per-segment identity; absent on legacy/router parts,\n * which collapse to a single logical text stream. Never invented client-side. */\nexport interface ChatTextPart {\n type: 'text'\n text: string\n id?: string\n}\n\nexport interface ChatReasoningPart {\n type: 'reasoning'\n text: string\n id?: string\n time?: ChatPartTime\n}\n\n/** Superset of the sidecar's status enum (`pending|running|completed|failed`)\n * and agent-interface's `ToolState` statuses; `error` is the persisted\n * terminal form `/stream`'s `normalizePersistedPart` settles on. */\nexport type ChatToolStatus = 'pending' | 'running' | 'completed' | 'error' | 'failed'\n\nexport interface ChatToolState {\n status: ChatToolStatus\n input?: unknown\n output?: unknown\n error?: string\n title?: string\n metadata?: Record<string, unknown>\n time?: ChatPartTime\n}\n\nexport interface ChatToolPart {\n type: 'tool'\n id: string\n tool: string\n callID?: string\n state: ChatToolState\n}\n\n/** Union of the sidecar's legacy (path-based) and AI-SDK (url-based) file\n * shapes; response-side every field besides `type` is optional. `name` is the\n * attachment-flavored display label an uploaded/promoted file carries (see\n * {@link ChatAttachmentPart}) — absent on a harness-emitted file part, which\n * uses `filename`. */\nexport interface ChatFilePart {\n type: 'file'\n id?: string\n filename?: string\n name?: string\n mediaType?: string\n url?: string\n path?: string\n content?: string\n}\n\nexport interface ChatImagePart {\n type: 'image'\n filename?: string\n name?: string\n mediaType?: string\n url?: string\n path?: string\n}\n\nexport interface ChatSubtaskPart {\n type: 'subtask'\n prompt: string\n description: string\n agent: string\n id?: string\n}\n\n/** OpenCode step-boundary marker — no renderable text; preserved so mappers\n * never coerce it into a \"[object Object]\" text part. */\nexport interface ChatStepStartPart {\n type: 'step-start'\n}\n\n/** Per-step usage receipt as the harness reports it (sidecar\n * `StepFinishPartSchema`). The message-level token/cost columns are this\n * shape flattened. */\nexport interface ChatUsageTokens {\n total?: number\n input?: number\n output?: number\n reasoning?: number\n cache?: {\n write?: number\n read?: number\n }\n}\n\nexport interface ChatStepFinishPart {\n type: 'step-finish'\n reason?: string\n tokens?: ChatUsageTokens\n cost?: number\n}\n\n/** Persisted human-in-the-loop ask — byte-matches\n * `interactionToPersistedPart` in `/web-react`'s chat-interactions contract. */\nexport interface ChatInteractionPart {\n type: 'interaction'\n id: string\n kind: string\n title: string\n body?: string\n answerSpec: { fields: ChatInteractionField[] }\n status: ChatInteractionStatus\n answers?: InteractionAnswers\n cancelReason?: string\n}\n\nexport type ChatPlanPart = ChatPlanPersistedPart\n\n/** Persisted one-line transcript notice — byte-matches `noticePart` in\n * `/web-react`'s chat-interactions contract. */\nexport interface ChatNoticePart {\n type: 'notice'\n id: string\n noticeKind: NoticeKind\n text: string\n}\n\n/**\n * A file the user `@`-mentioned on this turn: a workspace-relative path into\n * the sandbox, never bytes. `type: 'mention'` is its own discriminant\n * precisely so it does NOT collide with the `file`/`image` attachment parts —\n * an attachment carries content the product uploaded, a mention points at\n * something the box already has, and a transcript renders them differently\n * (an inline pill, not an attachment card).\n *\n * `path` is the identity: mentioning one file twice in a turn folds to one\n * part. `turnId` is optional and set by products that rebuild a turn's\n * mentions on retry.\n */\nexport interface ChatMentionPart {\n type: 'mention'\n mentionKind: ChatMentionKind\n path: string\n name: string\n size?: number\n turnId?: string\n}\n\n// The \"byte-matches\" claims above, enforced at compile time: the interaction\n// contract's codec output types and the stored part types must stay mutually\n// assignable, so a codec field added on one side without the other fails here.\ntype MutuallyAssignable<A extends B, B> = A\ntype _CodecEmitsStorableInteractionPart = MutuallyAssignable<InteractionPersistedPart, ChatInteractionPart>\ntype _StoredInteractionPartFeedsCodec = MutuallyAssignable<ChatInteractionPart, InteractionPersistedPart>\ntype _CodecEmitsStorableNoticePart = MutuallyAssignable<NoticePersistedPart, ChatNoticePart>\ntype _StoredNoticePartFeedsCodec = MutuallyAssignable<ChatNoticePart, NoticePersistedPart>\ntype _CodecEmitsStorablePlanPart = MutuallyAssignable<ChatPlanPersistedPart, ChatPlanPart>\ntype _StoredPlanPartFeedsCodec = MutuallyAssignable<ChatPlanPart, ChatPlanPersistedPart>\n\nexport type ChatMessagePart =\n | ChatTextPart\n | ChatReasoningPart\n | ChatToolPart\n | ChatFilePart\n | ChatImagePart\n | ChatSubtaskPart\n | ChatStepStartPart\n | ChatStepFinishPart\n | ChatInteractionPart\n | ChatNoticePart\n | ChatPlanPart\n | ChatMentionPart\n\n/** Every canonical harness wire-part kind must be storable — compile-time\n * guarantee that a new agent-interface part kind cannot silently fall out of\n * the persisted vocabulary. */\nexport type StorableHarnessPartKind = HarnessWirePart['type'] & ChatMessagePart['type']\n\n/**\n * The typed projection at the `/stream` → `/chat-store` boundary. The stream\n * normalizers (`normalizePersistedPart`/`mergePersistedPart`/\n * `finalizeAssistantParts`) deliberately produce untyped `JsonRecord`s — they\n * normalize wire shapes and do not own the stored vocabulary. THIS module\n * owns it, so this is where rows gain the `ChatMessagePart` type: each entry\n * is validated against its kind's required fields and narrowed, junk is\n * dropped, and — enforced by the exhaustiveness check below — no storable\n * kind can silently fall out (the step-finish/interaction trap).\n */\nexport function toChatMessageParts(parts: Array<Record<string, unknown>>): ChatMessagePart[] {\n const out: ChatMessagePart[] = []\n for (const part of parts) {\n const typed = toChatMessagePart(part)\n if (typed) out.push(typed)\n }\n return out\n}\n\nconst str = (value: unknown): value is string => typeof value === 'string'\n\nfunction toChatMessagePart(part: Record<string, unknown>): ChatMessagePart | null {\n if (!part || typeof part !== 'object') return null\n const type = part.type as ChatMessagePart['type'] | undefined\n switch (type) {\n case 'text':\n case 'reasoning':\n return str(part.text) ? (part as unknown as ChatTextPart | ChatReasoningPart) : null\n case 'tool':\n return str(part.id) && str(part.tool) && part.state && typeof part.state === 'object'\n ? (part as unknown as ChatToolPart)\n : null\n case 'file':\n case 'image':\n return part as unknown as ChatFilePart | ChatImagePart\n case 'subtask':\n return str(part.prompt) && str(part.description) && str(part.agent)\n ? (part as unknown as ChatSubtaskPart)\n : null\n case 'step-start':\n return { type: 'step-start' }\n case 'step-finish':\n return part as unknown as ChatStepFinishPart\n case 'interaction':\n return persistedPartToInteraction(part) ? (part as unknown as ChatInteractionPart) : null\n case 'notice':\n return str(part.id) && str(part.noticeKind) && str(part.text)\n ? (part as unknown as ChatNoticePart)\n : null\n case 'plan': {\n const plan = persistedPartToPlan(part)\n return plan ? ({ ...part, ...planToPersistedPart(plan) } as ChatPlanPart) : null\n }\n case 'mention':\n return isChatMentionPart(part) ? part : null\n case undefined:\n return null\n default: {\n // Compile-time exhaustiveness: a new ChatMessagePart kind that is not\n // handled above makes `type` non-never here and this line fails.\n const _exhaustive: never = type\n void _exhaustive\n return null\n }\n }\n}\n\nexport function isChatToolPart(part: ChatMessagePart): part is ChatToolPart {\n return part.type === 'tool'\n}\n\nexport function isChatTextPart(part: ChatMessagePart): part is ChatTextPart {\n return part.type === 'text'\n}\n\nexport function isChatInteractionPart(part: ChatMessagePart): part is ChatInteractionPart {\n return part.type === 'interaction'\n}\n\nexport function isChatPlanPart(part: ChatMessagePart): part is ChatPlanPart {\n return part.type === 'plan'\n}\n\nexport function isChatStepFinishPart(part: ChatMessagePart): part is ChatStepFinishPart {\n return part.type === 'step-finish'\n}\n\n/** Widened to `unknown` — unlike its siblings this guard also runs over raw\n * untyped stored rows (a transcript renderer reads `message.parts` before the\n * typed projection), which is exactly what {@link mentionPartsFromMessageParts}\n * needs. `path` and `name` carry the pill; a row missing either is unrenderable.\n *\n * Mirrors the write contract exactly (`parseFileMention` in `/chat-routes`,\n * then {@link mentionInputToPart}): a blank `name` is rejected there and so is\n * rejected here, and `size` — optional, but typed `number` once present — is\n * type-checked so `'12'` or `null` cannot ride through the guard wearing a\n * type it does not have. Negative sizes are NOT re-rejected: the wire screens\n * them, `mentionInputToPart` trusts its input, and a read guard stricter than\n * what the writer can emit would drop rows it produced itself. */\nexport function isChatMentionPart(part: unknown): part is ChatMentionPart {\n if (!part || typeof part !== 'object') return false\n const record = part as Record<string, unknown>\n if (record.size !== undefined && (typeof record.size !== 'number' || !Number.isFinite(record.size))) {\n return false\n }\n return (\n record.type === 'mention' &&\n typeof record.path === 'string' &&\n record.path.length > 0 &&\n typeof record.name === 'string' &&\n record.name.trim().length > 0 &&\n (record.mentionKind === 'image' || record.mentionKind === 'file')\n )\n}\n\n/** Every mention part on one message, in stored order. The projection a\n * transcript renderer runs before deciding which mentions the message text\n * already shows inline (see `segmentMentionContent` in `/web-react`). */\nexport function mentionPartsFromMessageParts(\n parts: ReadonlyArray<Record<string, unknown>> | ReadonlyArray<ChatMessagePart> | null | undefined,\n): ChatMentionPart[] {\n if (!parts) return []\n return (parts as ReadonlyArray<unknown>).filter(isChatMentionPart)\n}\n\n/** A validated wire mention (`parseFileMentions` in `/chat-routes`) as the\n * part the turn route persists. An absent/non-finite `size` is DROPPED rather\n * than stored as `undefined`, so a stored row never carries a key that means\n * nothing. */\nexport function mentionInputToPart(input: FileMention): ChatMentionPart {\n const part: ChatMentionPart = {\n type: 'mention',\n mentionKind: mentionKindForPath(input.path),\n path: input.path,\n name: input.name,\n }\n if (typeof input.size === 'number' && Number.isFinite(input.size)) part.size = input.size\n return part\n}\n\n// ── attachments ──────────────────────────────────────────────────────────\n//\n// An attachment is a file the product uploaded into its own store (vault /\n// object-store) ahead of the turn — content the box did not already have,\n// unlike a `@`-mention. It persists NOT as a new `ChatMessagePart` union\n// member but as an attachment-flavored `file`/`image` part: `type` is the\n// existing `image`/`file` discriminant, with `path` + `name` load-bearing.\n// That reuse is deliberate — a transcript already renders `file`/`image`\n// parts, and `toChatMessagePart` passes them through by cast (fields survive),\n// so an attachment slots into the same stored `parts` array without a parallel\n// vocabulary. These helpers own the input→part projection, the path-keyed\n// stream key, and the agent-facing prompt block, all byte-identical to the gtm\n// source they were lifted from (gtm-agent#618 adoption reproduces its rows and\n// dispatched prompt bytes through them).\n\n/** `image` for any `image/*` mime, `file` for everything else (including an\n * absent/empty mime) — the same split the composer and the persisted-part\n * discriminant both key on. */\nexport function attachmentKindForMime(mime?: string): ChatAttachmentKind {\n return mime?.startsWith('image/') ? 'image' : 'file'\n}\n\n/** Stream/transcript part key for an attachment — ONE declaration, owned by\n * `/stream`'s normalizer (whose `getPartKey` routes path-bearing file/image\n * parts through it). Re-exported here (not redefined) so the root barrel's\n * star-exports of both modules resolve to the same symbol instead of\n * colliding (TS2308 in the dts build). */\nexport { attachmentPartKey } from '../stream/stream-normalizer'\n\n/**\n * Persisted attachment part: structurally an attachment-flavored `ChatFilePart`\n * / `ChatImagePart` (`path` + `name` promoted to required) rather than a\n * separate union member — see the section note above. The index signature keeps\n * it a `Record<string, unknown>` so it drops into the same untyped `parts`\n * arrays every other stored part rides in, and lets the `type`-driven\n * constructors below build it without union-discriminant gymnastics.\n */\nexport interface ChatAttachmentPart {\n type: ChatAttachmentKind\n path: string\n name: string\n size?: number\n mediaType?: string\n turnId?: string\n [key: string]: unknown\n}\n\n/** True for any `image`/`file` part carrying a non-empty string `path`.\n * DELIBERATELY also matches a sidecar path-bearing file part (a mention-style\n * `file` part with only `path`) — the guard is a structural attachment shape,\n * not proof of provenance; a caller that needs to exclude those filters\n * upstream. The `path.length > 0` check diverges from gtm's original guard\n * (which only checked `typeof`), mirroring sibling {@link isChatMentionPart}:\n * read-side robustness against a stored `{type:'file', path:''}` row — which\n * would otherwise render a malformed `(vault: )` pointer line — justifies the\n * small divergence from byte-for-byte gtm parity here; no legitimate write\n * path ever produces an empty `path`. */\nexport function isChatAttachmentPart(part: unknown): part is ChatAttachmentPart {\n if (!part || typeof part !== 'object') return false\n const record = part as Record<string, unknown>\n return (\n (record.type === 'image' || record.type === 'file') &&\n typeof record.path === 'string' &&\n record.path.length > 0\n )\n}\n\n/** Drop absent/empty optional fields rather than persisting `undefined`/`''`\n * — keeps stored parts minimal. */\nexport function attachmentInputToPart(input: ChatAttachmentInput): ChatAttachmentPart {\n const part: ChatAttachmentPart = { type: input.kind, path: input.path, name: input.name }\n if (typeof input.size === 'number' && Number.isFinite(input.size)) part.size = input.size\n if (input.mediaType) part.mediaType = input.mediaType\n return part\n}\n\n/** Every attachment part on one message's stored `parts`, in stored order. */\nexport function attachmentPartsFromMessageParts(\n parts: ReadonlyArray<Record<string, unknown>> | null | undefined,\n): ChatAttachmentPart[] {\n if (!parts) return []\n return parts.filter(isChatAttachmentPart)\n}\n\n/** gtm's exact default header for {@link buildAttachmentPromptBlock} — kept\n * byte-identical because it feeds dispatched prompts (gtm-agent#618\n * reproduces the prompt bytes through this module). Override only with intent. */\nexport const DEFAULT_ATTACHMENT_PROMPT_HEADER =\n 'Attached files (already saved to the workspace vault — read them from these paths):'\n\n/** Same C0-control/DEL sweep as `/chat-routes`'s `resolve-attachments.ts`\n * (kept as a separate literal rather than a shared import — this module is\n * the read side and must stay usable over PERSISTED rows the wire validator\n * never saw). Replaced with a single space, not stripped or collapsed: this\n * is defense-in-depth for legacy/corrupt stored rows that bypassed wire\n * validation, not the primary control — a name/path a `resolveChatAttachments`\n * call validated today never contains one, so this is a no-op on legitimate\n * input and gtm byte-compat holds. */\nconst PROMPT_BLOCK_CONTROL_CHARS = /[\\x00-\\x1F\\x7F]/g\n\n/**\n * The agent-facing pointer block appended to the dispatched prompt — never\n * persisted in `message.content`. Empty array → `''` so callers can append\n * unconditionally. The sole producer of this text: both the current turn's\n * dispatch and history projection ({@link historyContentWithAttachments}) build\n * it from here, so the two can never drift. `header` overrides the first line\n * ({@link DEFAULT_ATTACHMENT_PROMPT_HEADER}); the per-file line format is fixed.\n *\n * Every interpolated `name`/`path` is swept for control characters first\n * (newline/carriage-return/tab included) and each one replaced with a single\n * space — a single-line-per-file guarantee. Without it, a control character\n * riding through on a PERSISTED row (`historyContentWithAttachments` reads\n * whatever is already stored; a legacy or hand-edited row was never re-checked\n * against `resolveChatAttachments`) could inject extra lines — fabricated\n * pointer entries or instructions — into a prompt the agent trusts verbatim.\n * The wire-side validator is the primary control; this is the backstop for\n * rows it never saw.\n */\nexport function buildAttachmentPromptBlock(\n atts: ReadonlyArray<Pick<ChatAttachmentPart, 'name' | 'path'>>,\n header: string = DEFAULT_ATTACHMENT_PROMPT_HEADER,\n): string {\n if (atts.length === 0) return ''\n const clean = (value: string) => value.replace(PROMPT_BLOCK_CONTROL_CHARS, ' ')\n const lines = atts.map((a) => `- ${clean(a.name)} (vault: ${clean(a.path)})`)\n return `\\n\\n${header}\\n${lines.join('\\n')}`\n}\n\n/**\n * History projection for a persisted message: `content` unchanged when it\n * carries no attachment parts, else the block is appended once. The\n * no-double-annotation guarantee holds under the invariant that the block is\n * never persisted INTO `message.content` — it is appended only at read time,\n * by this function and the current turn's dispatch, both sourcing from\n * `attachmentPartsFromMessageParts` — never by content inspection. This\n * function does not (and cannot) detect an already-embedded block by scanning\n * `content` for its text: a message whose stored `content` happens to already\n * contain the block's bytes, alongside attachment parts, still gets the block\n * appended a second time. See the pinned test for that boundary.\n */\nexport function historyContentWithAttachments(\n message: {\n content: string\n parts?: ReadonlyArray<Record<string, unknown>> | null\n },\n header?: string,\n): string {\n const attachmentParts = attachmentPartsFromMessageParts(message.parts)\n if (attachmentParts.length === 0) return message.content\n return `${message.content}${buildAttachmentPromptBlock(attachmentParts, header)}`\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/design-canvas-react/engine/command-stack.ts","../src/design-canvas-react/engine/commands.ts"],"sourcesContent":["/**\n * Undo/redo command stack over immutable `EditorSceneState`.\n *\n * History entries hold COMMANDS (state transforms + durable operations\n * captured at construction), never snapshots. Rebasing the document via\n * `reset()` therefore cannot stale the history: a later undo re-applies the\n * inverse transform to whatever document is current. If the rebase removed an\n * element a historical command targets, that transform throws (fail loud)\n * rather than silently editing the wrong element.\n *\n * `setView` updates volatile view state (zoom/pan/selection/toggles) without\n * touching history — view changes are never undo steps.\n */\n\nimport type { SceneDocument } from '../../design-canvas/model'\nimport type { EditorSceneState, SceneCommand, SceneCommandStack } from '../contracts'\n\n/** Oldest entries are dropped past this bound; redo stack is cleared on execute. */\nexport const SCENE_COMMAND_HISTORY_LIMIT = 200\n\n/**\n * The base {@link SceneCommandStack} plus the two command-specific recovery\n * primitives the undo/redo persistence path needs. `rollback` (on the base\n * contract) undoes a failed COMMIT; these undo a failed UNDO/REDO persist.\n * Defined here (not on the shared contract) so consumers that take the stack as\n * `ReturnType<typeof createSceneCommandStack>` see them without a contract bump.\n */\nexport interface SceneCommandStackWithReapply extends SceneCommandStack {\n /**\n * A persisted UNDO rejected: re-apply that command's FORWARD transform (move\n * it redo→undo). Mirrors `rollback`'s command-specific contract — it acts on\n * the captured command, not blindly on the redo-stack top, so an interleaved\n * edit cannot make it re-apply the wrong command. No-op if the command is not\n * the top of the redo stack (a newer edit reshaped history — the next\n * `reset()` reconciles).\n */\n reexecute(command: SceneCommand): void\n /**\n * A persisted REDO rejected: re-apply that command's INVERSE transform (move\n * it undo→redo). The redo-side mirror of `reexecute`. No-op unless the command\n * is the top of the undo stack.\n */\n reundo(command: SceneCommand): void\n}\n\nexport function createSceneCommandStack(document: SceneDocument, activePageId: string): SceneCommandStackWithReapply {\n let state: EditorSceneState = {\n document,\n activePageId,\n selectedElementIds: [],\n zoom: 1,\n panX: 0,\n panY: 0,\n gridEnabled: false,\n gridSize: 10,\n snapEnabled: true,\n showRulers: true,\n showBleed: false,\n }\n\n const undoStack: SceneCommand[] = []\n const redoStack: SceneCommand[] = []\n const listeners = new Set<() => void>()\n\n const notify = (): void => {\n for (const listener of [...listeners]) listener()\n }\n\n return {\n execute(command: SceneCommand): void {\n state = command.execute(state)\n undoStack.push(command)\n if (undoStack.length > SCENE_COMMAND_HISTORY_LIMIT) {\n undoStack.splice(0, undoStack.length - SCENE_COMMAND_HISTORY_LIMIT)\n }\n redoStack.length = 0\n notify()\n },\n\n // Both transforms run BEFORE the stacks move: a throwing transform (e.g.\n // missing element after reset()) leaves history and state exactly as they\n // were. The entry is never silently destroyed — the caller can retry after\n // the next server refresh restores the target.\n undo(): SceneCommand {\n const command = undoStack[undoStack.length - 1]\n if (!command) throw new Error('nothing to undo — guard with canUndo() before calling undo()')\n state = command.undo(state)\n undoStack.pop()\n redoStack.push(command)\n notify()\n return command\n },\n\n redo(): SceneCommand {\n const command = redoStack[redoStack.length - 1]\n if (!command) throw new Error('nothing to redo — guard with canRedo() before calling redo()')\n state = command.execute(state)\n redoStack.pop()\n undoStack.push(command)\n notify()\n return command\n },\n\n canUndo(): boolean {\n return undoStack.length > 0\n },\n\n canRedo(): boolean {\n return redoStack.length > 0\n },\n\n subscribe(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n\n getState(): EditorSceneState {\n return state\n },\n\n setView(patch: Partial<Omit<EditorSceneState, 'document'>>): void {\n state = { ...state, ...patch }\n notify()\n },\n\n // A persisted UNDO rejected: the server still has `command` applied, but the\n // local undo already removed it (and a later execute may have cleared the\n // redo stack). Deterministically re-converge — mirror of `rollback`, in the\n // forward direction: re-apply the command's FORWARD transform to current\n // state and ensure it lives on the undo stack (so a later undo can remove it\n // again). Idempotent: if the command is already on the undo stack (the undo\n // never actually left local history), this is a no-op. For disjoint edits the\n // forward transform commutes past commands executed while the undo was\n // pending, so their net effect is preserved.\n reexecute(command: SceneCommand): void {\n if (undoStack.includes(command)) return\n state = command.execute(state)\n const redoIdx = redoStack.lastIndexOf(command)\n if (redoIdx !== -1) redoStack.splice(redoIdx, 1)\n undoStack.push(command)\n notify()\n },\n\n // A persisted REDO rejected: the server does NOT have `command` applied, but\n // the local redo just applied it forward and put it on the undo stack.\n // Re-apply its INVERSE and move it to the redo stack — the redo-side mirror\n // of `reexecute`. No-op if the command is not on the undo stack.\n reundo(command: SceneCommand): void {\n const undoIdx = undoStack.lastIndexOf(command)\n if (undoIdx === -1) return\n state = command.undo(state)\n undoStack.splice(undoIdx, 1)\n if (!redoStack.includes(command)) redoStack.push(command)\n notify()\n },\n\n rollback(command: SceneCommand): void {\n const idx = undoStack.lastIndexOf(command)\n // Command not in history — stale or double-fire rejection handler; no-op.\n if (idx === -1) return\n\n // Apply the command's inverse against the current state. For non-overlapping\n // edits (different elements or different attributes) this is exact: the\n // inverse commutes freely past all subsequent commands. For overlapping edits\n // on the same attribute the result is defined but not semantically perfect;\n // the persistence layer should trigger an onResyncRequired refetch in that\n // case so the server document reconciles.\n state = command.undo(state)\n\n // Splice the target command out of the undo stack.\n undoStack.splice(idx, 1)\n\n // Clear the redo stack: its entries were computed relative to a history that\n // included this command, so they are now stale.\n redoStack.length = 0\n\n notify()\n },\n\n /** Rebase onto a server-refreshed document. History survives; selection\n * drops ids the refresh removed so view state never dangles. */\n reset(newDocument: SceneDocument): void {\n // Collect all live element ids across all pages for selection cleanup\n const liveElementIds = new Set<string>()\n for (const page of newDocument.pages) {\n collectElementIds(page.elements, liveElementIds)\n }\n // If the active page was removed, fall back to the first page\n const activePageExists = newDocument.pages.some((p) => p.id === state.activePageId)\n const activePageId = activePageExists ? state.activePageId : (newDocument.pages[0]?.id ?? state.activePageId)\n state = {\n ...state,\n document: newDocument,\n activePageId,\n selectedElementIds: state.selectedElementIds.filter((id) => liveElementIds.has(id)),\n }\n notify()\n },\n }\n}\n\nfunction collectElementIds(elements: ReturnType<typeof Array.prototype.slice>, ids: Set<string>): void {\n for (const el of elements as Array<{ id: string; kind: string; children?: unknown[] }>) {\n ids.add(el.id)\n if (el.kind === 'group' && Array.isArray(el.children)) {\n collectElementIds(el.children as Array<{ id: string; kind: string; children?: unknown[] }>, ids)\n }\n }\n}\n","/**\n * Concrete `SceneCommand` factories for the design-canvas editor. Every factory\n * captures the inverse from PRE-state at construction — undo is a value\n * computed once, never re-derived from current state later. Local execute/undo\n * update `EditorSceneState` immutably by routing through `applySceneOperation`\n * so optimistic state matches what the server-side dispatcher will persist.\n *\n * Drag gestures coalesce: pointer moves update volatile state, ONE command\n * executes on pointer release with (finalAttrs, priorAttrs). `setAttrsCommand`\n * is that gesture command — one undo step per drag/transform/toolbar change.\n * `multiSetAttrsCommand` collects N set_attrs ops into one undo step for\n * multi-select transforms.\n *\n * Group/ungroup route through `applySceneOperation` so the group-origin\n * rebasing in `apply.ts` is the single source of truth for both optimistic and\n * server state.\n */\n\nimport type { SceneDocument, SceneElement, ScenePage } from '../../design-canvas/model'\nimport { requireElement, requirePage } from '../../design-canvas/model'\nimport { applySceneOperation, applySceneOperations } from '../../design-canvas/apply'\nimport type {\n AddElementOperation,\n DeleteElementOperation,\n SceneAttrsPatch,\n SceneOperation,\n} from '../../design-canvas/operations'\nimport type { EditorSceneState, SceneCommand } from '../contracts'\n\n// ---------------------------------------------------------------------------\n// Shared helpers\n// ---------------------------------------------------------------------------\n\nfunction applyOps(state: EditorSceneState, ops: SceneOperation[]): EditorSceneState {\n return { ...state, document: applySceneOperations(state.document, ops) }\n}\n\nfunction applyOp(state: EditorSceneState, op: SceneOperation): EditorSceneState {\n return { ...state, document: applySceneOperation(state.document, op) }\n}\n\n// ---------------------------------------------------------------------------\n// add_element / inverse = delete_element\n// ---------------------------------------------------------------------------\n\nexport interface AddElementInput {\n pageId: string\n element: SceneElement\n /** Insertion z-index within owner; omitted → top. */\n index?: number\n /** Parent group id; omitted → page root. */\n parentGroupId?: string\n}\n\nexport function addElementCommand(input: AddElementInput): SceneCommand {\n const addOp: SceneOperation = {\n type: 'add_element',\n pageId: input.pageId,\n element: structuredClone(input.element),\n ...(input.index !== undefined ? { index: input.index } : {}),\n ...(input.parentGroupId !== undefined ? { parentGroupId: input.parentGroupId } : {}),\n }\n const deleteOp: SceneOperation = {\n type: 'delete_element',\n pageId: input.pageId,\n elementId: input.element.id,\n }\n\n return {\n label: `Add ${input.element.kind}`,\n execute: (state) => applyOp(state, addOp),\n undo: (state) => applyOp(state, deleteOp),\n operations: () => [structuredClone(addOp)],\n inverseOperations: () => [structuredClone(deleteOp)],\n }\n}\n\n// ---------------------------------------------------------------------------\n// set_attrs — THE gesture command (one undo step per drag/transform)\n// ---------------------------------------------------------------------------\n\nexport interface SetAttrsInput {\n pageId: string\n elementId: string\n /** Final attribute values after the gesture. */\n attrs: SceneAttrsPatch\n /** Attribute values BEFORE the gesture began — inverse is built from these. */\n priorAttrs: SceneAttrsPatch\n}\n\nexport function setAttrsCommand(input: SetAttrsInput): SceneCommand {\n const forwardOp: SceneOperation = {\n type: 'set_attrs',\n pageId: input.pageId,\n elementId: input.elementId,\n attrs: structuredClone(input.attrs),\n }\n const inverseOp: SceneOperation = {\n type: 'set_attrs',\n pageId: input.pageId,\n elementId: input.elementId,\n attrs: structuredClone(input.priorAttrs),\n }\n\n return {\n label: 'Edit element',\n execute: (state) => applyOp(state, forwardOp),\n undo: (state) => applyOp(state, inverseOp),\n operations: () => [structuredClone(forwardOp)],\n inverseOperations: () => [structuredClone(inverseOp)],\n }\n}\n\n// ---------------------------------------------------------------------------\n// multi-select set_attrs — N set_attrs ops, one undo step\n// ---------------------------------------------------------------------------\n\nexport interface MultiSetAttrsEntry {\n pageId: string\n elementId: string\n attrs: SceneAttrsPatch\n priorAttrs: SceneAttrsPatch\n}\n\nexport function multiSetAttrsCommand(entries: MultiSetAttrsEntry[]): SceneCommand {\n if (entries.length === 0) throw new Error('multiSetAttrsCommand: entries must not be empty')\n\n const forwardOps: SceneOperation[] = entries.map((e) => ({\n type: 'set_attrs' as const,\n pageId: e.pageId,\n elementId: e.elementId,\n attrs: structuredClone(e.attrs),\n }))\n const inverseOps: SceneOperation[] = entries.map((e) => ({\n type: 'set_attrs' as const,\n pageId: e.pageId,\n elementId: e.elementId,\n attrs: structuredClone(e.priorAttrs),\n }))\n\n return {\n label: `Edit ${entries.length} elements`,\n execute: (state) => applyOps(state, forwardOps),\n undo: (state) => applyOps(state, inverseOps),\n operations: () => structuredClone(forwardOps),\n inverseOperations: () => structuredClone(inverseOps),\n }\n}\n\n// ---------------------------------------------------------------------------\n// reorder_element\n// ---------------------------------------------------------------------------\n\nexport interface ReorderElementInput {\n pageId: string\n elementId: string\n toIndex: number\n}\n\nexport function reorderElementCommand(input: ReorderElementInput): SceneCommand {\n // Capture the element's current index at construction for the inverse\n let capturedFromIndex: number | null = null\n\n const forwardOp: SceneOperation = {\n type: 'reorder_element',\n pageId: input.pageId,\n elementId: input.elementId,\n toIndex: input.toIndex,\n }\n\n return {\n label: 'Reorder element',\n execute: (state) => {\n // Guard: on redo the element is already at toIndex, so we must not\n // overwrite the captured origin with the wrong position.\n if (capturedFromIndex === null) {\n const page = requirePage(state.document, input.pageId)\n const { index } = requireElement(page, input.elementId)\n capturedFromIndex = index\n }\n return applyOp(state, forwardOp)\n },\n undo: (state) => {\n if (capturedFromIndex === null) {\n throw new Error('reorderElementCommand: undo called before execute')\n }\n return applyOp(state, {\n type: 'reorder_element',\n pageId: input.pageId,\n elementId: input.elementId,\n toIndex: capturedFromIndex,\n })\n },\n operations: () => [structuredClone(forwardOp)],\n inverseOperations: () => {\n if (capturedFromIndex === null) {\n throw new Error('reorderElementCommand: inverseOperations called before execute')\n }\n return [{\n type: 'reorder_element' as const,\n pageId: input.pageId,\n elementId: input.elementId,\n toIndex: capturedFromIndex,\n }]\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// delete_element / inverse = add_element (full snapshot + index + parent)\n// ---------------------------------------------------------------------------\n\nexport interface DeleteElementInput {\n document: SceneDocument\n pageId: string\n elementId: string\n}\n\nexport function deleteElementCommand(input: DeleteElementInput): SceneCommand {\n const page = requirePage(input.document, input.pageId)\n const { element, owner, index } = requireElement(page, input.elementId)\n const snapshot = structuredClone(element)\n\n // Determine parentGroupId by finding which group owns this element\n const parentGroupId = findParentGroupId(page, owner)\n\n const deleteOp: DeleteElementOperation = {\n type: 'delete_element',\n pageId: input.pageId,\n elementId: input.elementId,\n }\n const addOp: AddElementOperation = {\n type: 'add_element',\n pageId: input.pageId,\n element: snapshot,\n index,\n ...(parentGroupId !== undefined ? { parentGroupId } : {}),\n }\n\n return {\n label: `Delete ${element.kind}`,\n execute: (state) => {\n const next = applyOp(state, deleteOp)\n return {\n ...next,\n selectedElementIds: next.selectedElementIds.filter((id) => id !== input.elementId),\n }\n },\n undo: (state) => applyOp(state, addOp),\n operations: () => [structuredClone(deleteOp)],\n inverseOperations: () => [structuredClone(addOp)],\n }\n}\n\nfunction findParentGroupId(page: ScenePage, owner: SceneElement[]): string | undefined {\n if (owner === page.elements) return undefined\n // Walk the element tree looking for a group whose children === owner\n return findGroupWithChildren(page.elements, owner)\n}\n\nfunction findGroupWithChildren(elements: SceneElement[], target: SceneElement[]): string | undefined {\n for (const el of elements) {\n if (el.kind === 'group') {\n if (el.children === target) return el.id\n const found = findGroupWithChildren(el.children, target)\n if (found !== undefined) return found\n }\n }\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// group_elements / inverse = ungroup_element\n// ---------------------------------------------------------------------------\n\nexport interface GroupElementsInput {\n document: SceneDocument\n pageId: string\n elementIds: string[]\n groupId: string\n name?: string\n}\n\nexport function groupElementsCommand(input: GroupElementsInput): SceneCommand {\n if (input.elementIds.length < 2) {\n throw new Error('groupElementsCommand: requires ≥ 2 elementIds')\n }\n\n const groupOp: SceneOperation = {\n type: 'group_elements',\n pageId: input.pageId,\n elementIds: input.elementIds.slice(),\n groupId: input.groupId,\n ...(input.name !== undefined ? { name: input.name } : {}),\n }\n const ungroupOp: SceneOperation = {\n type: 'ungroup_element',\n pageId: input.pageId,\n groupId: input.groupId,\n }\n\n return {\n label: `Group ${input.elementIds.length} elements`,\n execute: (state) => {\n const next = applyOp(state, groupOp)\n return {\n ...next,\n selectedElementIds: [input.groupId],\n }\n },\n undo: (state) => {\n const next = applyOp(state, ungroupOp)\n return { ...next, selectedElementIds: input.elementIds.slice() }\n },\n operations: () => [structuredClone(groupOp)],\n inverseOperations: () => [structuredClone(ungroupOp)],\n }\n}\n\n// ---------------------------------------------------------------------------\n// ungroup_element / inverse = group_elements (re-using original ids)\n// ---------------------------------------------------------------------------\n\nexport interface UngroupElementInput {\n document: SceneDocument\n pageId: string\n groupId: string\n}\n\nexport function ungroupElementCommand(input: UngroupElementInput): SceneCommand {\n const page = requirePage(input.document, input.pageId)\n const { element } = requireElement(page, input.groupId)\n if (element.kind !== 'group') {\n throw new Error(`ungroupElementCommand: element ${input.groupId} is kind ${element.kind}, not group`)\n }\n const childIds = element.children.map((c) => c.id)\n\n const ungroupOp: SceneOperation = {\n type: 'ungroup_element',\n pageId: input.pageId,\n groupId: input.groupId,\n }\n const regroupOp: SceneOperation = {\n type: 'group_elements',\n pageId: input.pageId,\n elementIds: childIds,\n groupId: input.groupId,\n name: element.name,\n }\n\n return {\n label: `Ungroup`,\n execute: (state) => {\n const next = applyOp(state, ungroupOp)\n return { ...next, selectedElementIds: childIds.slice() }\n },\n undo: (state) => {\n const next = applyOp(state, regroupOp)\n return { ...next, selectedElementIds: [input.groupId] }\n },\n operations: () => [structuredClone(ungroupOp)],\n inverseOperations: () => [structuredClone(regroupOp)],\n }\n}\n\n// ---------------------------------------------------------------------------\n// Page commands\n// ---------------------------------------------------------------------------\n\nexport interface AddPageInput {\n pageId: string\n options?: import('../../design-canvas/model').NewPageOptions\n index?: number\n}\n\nexport function addPageCommand(input: AddPageInput): SceneCommand {\n const addOp: SceneOperation = {\n type: 'add_page',\n pageId: input.pageId,\n ...(input.options !== undefined ? { options: input.options } : {}),\n ...(input.index !== undefined ? { index: input.index } : {}),\n }\n const deleteOp: SceneOperation = { type: 'delete_page', pageId: input.pageId }\n\n return {\n label: 'Add page',\n execute: (state) => {\n const next = applyOp(state, addOp)\n return { ...next, activePageId: input.pageId }\n },\n undo: (state) => {\n const next = applyOp(state, deleteOp)\n // Switch active page to first surviving page if we deleted the active one\n const activeExists = next.document.pages.some((p) => p.id === next.activePageId)\n return activeExists ? next : { ...next, activePageId: next.document.pages[0]!.id }\n },\n operations: () => [structuredClone(addOp)],\n inverseOperations: () => [structuredClone(deleteOp)],\n }\n}\n\nexport interface DuplicatePageInput {\n document: SceneDocument\n sourcePageId: string\n /** Caller-minted id for the copy. */\n pageId: string\n}\n\nexport function duplicatePageCommand(input: DuplicatePageInput): SceneCommand {\n requirePage(input.document, input.sourcePageId)\n\n const dupOp: SceneOperation = {\n type: 'duplicate_page',\n sourcePageId: input.sourcePageId,\n pageId: input.pageId,\n }\n const deleteOp: SceneOperation = { type: 'delete_page', pageId: input.pageId }\n\n return {\n label: 'Duplicate page',\n execute: (state) => {\n const next = applyOp(state, dupOp)\n return { ...next, activePageId: input.pageId }\n },\n undo: (state) => {\n const next = applyOp(state, deleteOp)\n const activeExists = next.document.pages.some((p) => p.id === next.activePageId)\n return activeExists ? next : { ...next, activePageId: input.sourcePageId }\n },\n operations: () => [structuredClone(dupOp)],\n inverseOperations: () => [structuredClone(deleteOp)],\n }\n}\n\nexport interface DeletePageInput {\n document: SceneDocument\n pageId: string\n}\n\nexport function deletePageCommand(input: DeletePageInput): SceneCommand {\n if (input.document.pages.length <= 1) {\n throw new Error('deletePageCommand: cannot delete the last page')\n }\n const page = requirePage(input.document, input.pageId)\n const currentIndex = input.document.pages.findIndex((p) => p.id === input.pageId)\n const snapshot = structuredClone(page)\n\n const deleteOp: SceneOperation = { type: 'delete_page', pageId: input.pageId }\n\n // Undo uses add_page + per-element add_element to restore the full snapshot.\n // The page shell is added first, then elements are inserted in z-order so\n // subsequent undo/redo round-trips see the correct stack. This also makes\n // inverseOperations() safe to emit server-side: the server will see the same\n // full restore instead of a bare shell.\n function buildRestoreOps(): SceneOperation[] {\n const ops: SceneOperation[] = [\n {\n type: 'add_page',\n pageId: snapshot.id,\n options: {\n name: snapshot.name,\n width: snapshot.width,\n height: snapshot.height,\n background: snapshot.background,\n },\n index: currentIndex,\n },\n ]\n if (snapshot.bleed) {\n ops.push({ type: 'set_page_props', pageId: snapshot.id, bleed: snapshot.bleed })\n }\n if (snapshot.guides.vertical.length > 0 || snapshot.guides.horizontal.length > 0) {\n ops.push({ type: 'set_page_guides', pageId: snapshot.id, guides: snapshot.guides })\n }\n for (let i = 0; i < snapshot.elements.length; i++) {\n ops.push({\n type: 'add_element',\n pageId: snapshot.id,\n element: structuredClone(snapshot.elements[i]!),\n index: i,\n })\n }\n return ops\n }\n\n return {\n label: 'Delete page',\n execute: (state) => {\n const next = applyOp(state, deleteOp)\n const activeExists = next.document.pages.some((p) => p.id === next.activePageId)\n if (activeExists) return next\n const fallbackIndex = Math.min(currentIndex, next.document.pages.length - 1)\n return { ...next, activePageId: next.document.pages[fallbackIndex]!.id }\n },\n undo: (state) => {\n const next = applyOps(state, buildRestoreOps())\n return { ...next, activePageId: input.pageId }\n },\n operations: () => [structuredClone(deleteOp)],\n inverseOperations: () => buildRestoreOps(),\n }\n}\n\nexport interface ReorderPageInput {\n pageId: string\n toIndex: number\n}\n\nexport function reorderPageCommand(input: ReorderPageInput): SceneCommand {\n let capturedFromIndex: number | null = null\n\n const forwardOp: SceneOperation = {\n type: 'reorder_page',\n pageId: input.pageId,\n toIndex: input.toIndex,\n }\n\n return {\n label: 'Reorder page',\n execute: (state) => {\n // Guard: on redo the page is already at toIndex; capture only on first execute.\n if (capturedFromIndex === null) {\n capturedFromIndex = state.document.pages.findIndex((p) => p.id === input.pageId)\n if (capturedFromIndex === -1) throw new Error(`reorderPageCommand: page ${input.pageId} not found`)\n }\n return applyOp(state, forwardOp)\n },\n undo: (state) => {\n if (capturedFromIndex === null) throw new Error('reorderPageCommand: undo called before execute')\n return applyOp(state, {\n type: 'reorder_page',\n pageId: input.pageId,\n toIndex: capturedFromIndex,\n })\n },\n operations: () => [structuredClone(forwardOp)],\n inverseOperations: () => {\n if (capturedFromIndex === null) throw new Error('reorderPageCommand: inverseOperations called before execute')\n return [{ type: 'reorder_page' as const, pageId: input.pageId, toIndex: capturedFromIndex }]\n },\n }\n}\n\nexport interface SetPagePropsInput {\n document: SceneDocument\n pageId: string\n props: {\n name?: string\n width?: number\n height?: number\n background?: string\n bleed?: import('../../design-canvas/model').PageBleed | null\n }\n}\n\nexport function setPagePropsCommand(input: SetPagePropsInput): SceneCommand {\n const page = requirePage(input.document, input.pageId)\n const prior: NonNullable<import('../../design-canvas/operations').SetPagePropsOperation> = {\n type: 'set_page_props',\n pageId: input.pageId,\n ...(input.props.name !== undefined ? { name: page.name } : {}),\n ...(input.props.width !== undefined ? { width: page.width } : {}),\n ...(input.props.height !== undefined ? { height: page.height } : {}),\n ...(input.props.background !== undefined ? { background: page.background } : {}),\n ...(input.props.bleed !== undefined ? { bleed: page.bleed } : {}),\n }\n\n const forwardOp: SceneOperation = {\n type: 'set_page_props',\n pageId: input.pageId,\n ...input.props,\n }\n\n return {\n label: 'Edit page',\n execute: (state) => applyOp(state, forwardOp),\n undo: (state) => applyOp(state, prior),\n operations: () => [structuredClone(forwardOp)],\n inverseOperations: () => [structuredClone(prior) as SceneOperation],\n }\n}\n\nexport interface SetPageGuidesInput {\n document: SceneDocument\n pageId: string\n guides: import('../../design-canvas/model').PageGuides\n}\n\nexport function setPageGuidesCommand(input: SetPageGuidesInput): SceneCommand {\n const page = requirePage(input.document, input.pageId)\n const priorGuides = structuredClone(page.guides)\n\n const forwardOp: SceneOperation = {\n type: 'set_page_guides',\n pageId: input.pageId,\n guides: structuredClone(input.guides),\n }\n const inverseOp: SceneOperation = {\n type: 'set_page_guides',\n pageId: input.pageId,\n guides: priorGuides,\n }\n\n return {\n label: 'Edit guides',\n execute: (state) => applyOp(state, forwardOp),\n undo: (state) => applyOp(state, inverseOp),\n operations: () => [structuredClone(forwardOp)],\n inverseOperations: () => [structuredClone(inverseOp)],\n }\n}\n\n// ---------------------------------------------------------------------------\n// bind_slot\n// ---------------------------------------------------------------------------\n\nexport interface BindSlotInput {\n document: SceneDocument\n pageId: string\n elementId: string\n slot: string | null\n}\n\nexport function bindSlotCommand(input: BindSlotInput): SceneCommand {\n const page = requirePage(input.document, input.pageId)\n const { element } = requireElement(page, input.elementId)\n const priorSlot = element.slot ?? null\n\n const forwardOp: SceneOperation = {\n type: 'bind_slot',\n pageId: input.pageId,\n elementId: input.elementId,\n slot: input.slot,\n }\n const inverseOp: SceneOperation = {\n type: 'bind_slot',\n pageId: input.pageId,\n elementId: input.elementId,\n slot: priorSlot,\n }\n\n return {\n label: input.slot === null ? 'Unbind slot' : `Bind slot \"${input.slot}\"`,\n execute: (state) => applyOp(state, forwardOp),\n undo: (state) => applyOp(state, inverseOp),\n operations: () => [structuredClone(forwardOp)],\n inverseOperations: () => [structuredClone(inverseOp)],\n }\n}\n\n// ---------------------------------------------------------------------------\n// set_document_title\n// ---------------------------------------------------------------------------\n\nexport interface SetDocumentTitleInput {\n document: SceneDocument\n title: string\n}\n\nexport function setDocumentTitleCommand(input: SetDocumentTitleInput): SceneCommand {\n const priorTitle = input.document.title\n\n const forwardOp: SceneOperation = { type: 'set_document_title', title: input.title }\n const inverseOp: SceneOperation = { type: 'set_document_title', title: priorTitle }\n\n return {\n label: `Rename document`,\n execute: (state) => applyOp(state, forwardOp),\n undo: (state) => applyOp(state, inverseOp),\n operations: () => [structuredClone(forwardOp)],\n inverseOperations: () => [structuredClone(inverseOp)],\n }\n}\n"],"mappings":"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","names":["activePageId"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tangle/index.ts"],"sourcesContent":["/**\n * Tangle login + the developer self-service app-registration → broker-token\n * flow, for apps built on agent-app.\n *\n * The platform (agent-dev-container integration hub) lets a developer register\n * their client app and obtain a `sk-tan-broker-` bearer to call `/v1/hub/exec`\n * on a user's connected integrations — WITHOUT being a hard-coded \"trusted app\".\n * The wire client (`TangleAppsClient` — registerApp / exchangeAuthCode /\n * mintBrokerToken) lives in `@tangle-network/agent-integrations`; this module is\n * the app-shell layer on top, and is intentionally **structural**: it depends on\n * the minter CONTRACT, not the concrete client, so it installs without the\n * agent-integrations publish and is trivially testable. A consumer constructs\n * the real client and passes it in.\n *\n * 1. {@link buildConsentUrl} — send the user through the ONE-TIME consent\n * (their Tangle session authorizes the app for a connection + scopes).\n * 2. On the callback, the consumer's client `exchangeAuthCode`s the `agc_`\n * code into the first broker token + a durable grant.\n * 3. {@link createBrokerTokenProvider} — the runtime path: a cached provider\n * that re-mints a fresh single-use broker token per `/v1/hub/exec` from the\n * durable grant using only the app credentials (no user session). Caches\n * until just before expiry so a burst of hub calls shares one mint.\n */\n\n/** A single-use hub bearer minted from a durable grant — mirrors\n * `@tangle-network/agent-integrations`'s `BrokerToken`. */\nexport interface BrokerToken {\n /** The `sk-tan-broker-…` bearer for a single `/v1/hub/exec` call. */\n accessToken: string\n /** Seconds until expiry. */\n expiresIn: number\n scope: string\n connectionId?: string\n}\n\n/** The one method the provider needs — `TangleAppsClient` satisfies it\n * structurally, so `createBrokerTokenProvider({ client: tangleAppsClient, … })`\n * type-checks without importing the concrete class. */\nexport interface BrokerTokenMinter {\n mintBrokerToken(input: { clientId: string; clientSecret: string; grantId: string; ttlSeconds?: number }): Promise<BrokerToken>\n}\n\nexport interface ConsentUrlInput {\n /** Platform base URL (e.g. https://id.tangle.tools). */\n endpoint: string\n clientId: string\n /** Must match one of the app's registered redirect URIs. */\n redirectUri: string\n /** Scopes the app is requesting for this connection (e.g. ['gmail.read']). */\n scopes: string[]\n /** Opaque CSRF/state value the callback echoes back — verify it on return. */\n state: string\n /** Optionally pre-select a specific connection to authorize. */\n connectionId?: string\n}\n\n/**\n * Build the URL to send the user to for the one-time app-consent. The user's\n * Tangle session (not the app's credentials) authorizes it; on approval the\n * platform redirects to `redirectUri?code=agc_…&state=…`.\n */\nexport function buildConsentUrl(input: ConsentUrlInput): string {\n const base = input.endpoint.replace(/\\/+$/, '')\n const params = new URLSearchParams({\n client_id: input.clientId,\n redirect_uri: input.redirectUri,\n scope: input.scopes.join(' '),\n state: input.state,\n response_type: 'code',\n })\n if (input.connectionId) params.set('connection_id', input.connectionId)\n return `${base}/cross-site/app-consent?${params.toString()}`\n}\n\nexport interface BrokerTokenProviderOptions {\n client: BrokerTokenMinter\n clientId: string\n clientSecret: string\n /** The durable grant id from the consent exchange. */\n grantId: string\n /** Requested token TTL (seconds). */\n ttlSeconds?: number\n /** Re-mint this many ms BEFORE expiry so an in-flight call never uses a\n * just-expired token. Default 30s. */\n refreshSkewMs?: number\n /** Injectable clock (ms). Default `Date.now`. */\n now?: () => number\n}\n\nexport interface BrokerTokenProvider {\n /** A valid `sk-tan-broker-` bearer, minting/refreshing as needed. */\n getToken(): Promise<string>\n /** Force the next `getToken` to re-mint (e.g. after a 401 from the hub). */\n invalidate(): void\n}\n\n/**\n * Cache + auto-refresh a broker token for one grant. A burst of hub calls\n * shares a single mint; the token is re-minted once it's within `refreshSkewMs`\n * of expiry, or on demand via {@link BrokerTokenProvider.invalidate}.\n * Concurrent `getToken` calls during a mint share the same in-flight promise\n * (no thundering herd).\n */\nexport function createBrokerTokenProvider(opts: BrokerTokenProviderOptions): BrokerTokenProvider {\n const now = opts.now ?? (() => Date.now())\n const skew = opts.refreshSkewMs ?? 30_000\n let cached: { token: string; expiresAt: number } | null = null\n let inflight: Promise<string> | null = null\n\n async function mint(): Promise<string> {\n const t = await opts.client.mintBrokerToken({\n clientId: opts.clientId,\n clientSecret: opts.clientSecret,\n grantId: opts.grantId,\n ttlSeconds: opts.ttlSeconds,\n })\n cached = { token: t.accessToken, expiresAt: now() + t.expiresIn * 1000 }\n return t.accessToken\n }\n\n return {\n async getToken() {\n if (cached && now() < cached.expiresAt - skew) return cached.token\n if (inflight) return inflight\n inflight = mint().finally(() => {\n inflight = null\n })\n return inflight\n },\n invalidate() {\n cached = null\n },\n }\n}\n"],"mappings":";AA6DO,SAAS,gBAAgB,OAAgC;AAC9D,QAAM,OAAO,MAAM,SAAS,QAAQ,QAAQ,EAAE;AAC9C,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,WAAW,MAAM;AAAA,IACjB,cAAc,MAAM;AAAA,IACpB,OAAO,MAAM,OAAO,KAAK,GAAG;AAAA,IAC5B,OAAO,MAAM;AAAA,IACb,eAAe;AAAA,EACjB,CAAC;AACD,MAAI,MAAM,aAAc,QAAO,IAAI,iBAAiB,MAAM,YAAY;AACtE,SAAO,GAAG,IAAI,2BAA2B,OAAO,SAAS,CAAC;AAC5D;AA+BO,SAAS,0BAA0B,MAAuD;AAC/F,QAAM,MAAM,KAAK,QAAQ,MAAM,KAAK,IAAI;AACxC,QAAM,OAAO,KAAK,iBAAiB;AACnC,MAAI,SAAsD;AAC1D,MAAI,WAAmC;AAEvC,iBAAe,OAAwB;AACrC,UAAM,IAAI,MAAM,KAAK,OAAO,gBAAgB;AAAA,MAC1C,UAAU,KAAK;AAAA,MACf,cAAc,KAAK;AAAA,MACnB,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,IACnB,CAAC;AACD,aAAS,EAAE,OAAO,EAAE,aAAa,WAAW,IAAI,IAAI,EAAE,YAAY,IAAK;AACvE,WAAO,EAAE;AAAA,EACX;AAEA,SAAO;AAAA,IACL,MAAM,WAAW;AACf,UAAI,UAAU,IAAI,IAAI,OAAO,YAAY,KAAM,QAAO,OAAO;AAC7D,UAAI,SAAU,QAAO;AACrB,iBAAW,KAAK,EAAE,QAAQ,MAAM;AAC9B,mBAAW;AAAA,MACb,CAAC;AACD,aAAO;AAAA,IACT;AAAA,IACA,aAAa;AACX,eAAS;AAAA,IACX;AAAA,EACF;AACF;","names":[]}
|