@underdogai/mesh-event-schemas 0.2.0 → 0.6.0

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.
Files changed (45) hide show
  1. package/dist/apppacks.d.ts +1242 -385
  2. package/dist/apppacks.d.ts.map +1 -1
  3. package/dist/apppacks.js +1 -1
  4. package/dist/blocks.d.ts +295 -58
  5. package/dist/blocks.d.ts.map +1 -1
  6. package/dist/blocks.js +90 -11
  7. package/dist/blocks.js.map +1 -1
  8. package/dist/capabilities.d.ts +2 -1
  9. package/dist/capabilities.d.ts.map +1 -1
  10. package/dist/capabilities.js +2 -1
  11. package/dist/capabilities.js.map +1 -1
  12. package/dist/events.d.ts +456 -95
  13. package/dist/events.d.ts.map +1 -1
  14. package/dist/events.js +109 -1
  15. package/dist/events.js.map +1 -1
  16. package/dist/geo.d.ts +52 -0
  17. package/dist/geo.d.ts.map +1 -0
  18. package/dist/geo.js +65 -0
  19. package/dist/geo.js.map +1 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/namespace.d.ts +2 -0
  25. package/dist/namespace.d.ts.map +1 -1
  26. package/dist/namespace.js +2 -0
  27. package/dist/namespace.js.map +1 -1
  28. package/dist/registry.d.ts +3 -2
  29. package/dist/registry.d.ts.map +1 -1
  30. package/dist/registry.js +25 -3
  31. package/dist/registry.js.map +1 -1
  32. package/dist/surfaces.d.ts +47195 -12148
  33. package/dist/surfaces.d.ts.map +1 -1
  34. package/dist/surfaces.js +141 -3
  35. package/dist/surfaces.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/apppacks.ts +1 -1
  38. package/src/blocks.ts +92 -11
  39. package/src/capabilities.ts +2 -1
  40. package/src/events.ts +119 -4
  41. package/src/geo.ts +75 -0
  42. package/src/index.ts +1 -0
  43. package/src/namespace.ts +2 -0
  44. package/src/registry.ts +26 -2
  45. package/src/surfaces.ts +155 -2
package/src/registry.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  CommandContentSchema,
18
18
  EntitySnapshotContentSchema,
19
19
  ErpEventContentSchema,
20
+ FilePreviewContentSchema,
20
21
  NoteBlockContentSchema,
21
22
  NoteEditingContentSchema,
22
23
  NoteMetaContentSchema,
@@ -200,6 +201,14 @@ export const EVENT_REGISTRY: RegistryEntry[] = [
200
201
  publishableBy: ["agent"],
201
202
  description: "에이전트 → 에이전트 위임 결과 회신 (A2A)",
202
203
  },
204
+ {
205
+ type: EventType.FilePreview,
206
+ kind: "message",
207
+ schema: FilePreviewContentSchema,
208
+ publishableBy: ["agent", "bridge"],
209
+ description:
210
+ "파생 미리보기 이미지 (org.corp.file.preview) — 변환기 봇이 발행하고 원본 파일 이벤트를 m.reference. pending→ready/failed 를 새 이벤트로 흘리고 최신이 이긴다. 사람 발행 금지: 클라가 원본 타일을 이 이미지로 대체하는 신뢰 통로다",
211
+ },
203
212
  ];
204
213
 
205
214
  /** 플러그인이 런타임에 추가한 엔트리(org.corp.<domain>.*). 코어 뒤에 둔다 = 코어 우선. */
@@ -269,11 +278,25 @@ export function canPublish(type: string, publisher: Publisher): boolean {
269
278
  }
270
279
 
271
280
  /**
272
- * 발신자 MXID 를 발행자 분류(human/agent/bridge)로 매핑한다 (docs/tickets/SEC-3).
281
+ * AS 네임스페이스 **밖**에 사는 봇 계정 — 접두사 관례로는 못 잡아 정확 일치로 인정한다.
282
+ *
283
+ * 이 홈서버의 mesh-bots 가 그렇다(`@mesh-bot`): `@agent_*` 도 `@erp_*` 도 아니라 human 으로 떨어지고,
284
+ * 그러면 file.preview 처럼 봇만 발행할 수 있는 타입이 **발행은 성공하는데 수신측에서 통째로 버려진다**
285
+ * — 화면엔 아무것도 안 나오고 이유도 로그에 안 남는 실패다.
286
+ *
287
+ * ⚠️ 부분 일치("bot 이 들어가면")로 넓히지 말 것. 이 함수는 파일 미리보기 전용이 아니라 **전 이벤트의
288
+ * 권한 게이트**이고, `ui.action` 은 `["human","agent"]` 라 bridge 가 못 보낸다 — `@robot` 같은 사람
289
+ * 계정이 봇 매칭에 걸리면 그 사람의 카드 버튼이 조용히 막힌다.
290
+ */
291
+ const BRIDGE_LOCALPARTS = new Set(["@mesh-bot"]);
292
+
293
+ /**
294
+ * 발신자 MXID 를 발행자 분류(human/agent/bridge)로 매핑한다 (규범: docs/event-model.md §발행자 권한).
273
295
  *
274
296
  * 서버가 인바운드 이벤트를 받을 때 `canPublish(type, classifySender(sender))` 로 "이 발신자가 이 타입을
275
297
  * 보낼 자격이 있는가"를 강제하는 데 쓴다. 분류는 AS 독점 네임스페이스 관례를 따른다
276
- * (infra/registration: `@agent_*` = 에이전트, `@erp_*` = 브리지, 그 외 = 사람).
298
+ * (infra/registration: `@agent_*` = 에이전트, `@erp_*` = 브리지, 그 외 = 사람) — 그 관례 밖의 봇
299
+ * 계정은 BRIDGE_LOCALPARTS 로 정확히 하나씩 인정한다.
277
300
  *
278
301
  * ⚠️ 반드시 홈서버가 서명·귀속한 event.sender 로 호출할 것 — 본문(content)의 from_* 필드는 위조 가능하다.
279
302
  */
@@ -281,5 +304,6 @@ export function classifySender(senderId: string): Publisher {
281
304
  const localpart = senderId.split(":")[0] ?? "";
282
305
  if (localpart.startsWith("@agent_")) return "agent";
283
306
  if (localpart.startsWith("@erp_")) return "bridge";
307
+ if (BRIDGE_LOCALPARTS.has(localpart)) return "bridge";
284
308
  return "human";
285
309
  }
package/src/surfaces.ts CHANGED
@@ -12,6 +12,7 @@ import { z } from "zod";
12
12
  import {
13
13
  CheckboxBlockSchema,
14
14
  InputBlockSchema,
15
+ LocationBlockSchema,
15
16
  RadioBlockSchema,
16
17
  SelectBlockSchema,
17
18
  SliderBlockSchema,
@@ -428,6 +429,7 @@ export const ModalFormBlockSchema = z.discriminatedUnion("type", [
428
429
  CheckboxBlockSchema,
429
430
  ToggleBlockSchema,
430
431
  SliderBlockSchema,
432
+ LocationBlockSchema,
431
433
  ]);
432
434
  export type ModalFormBlock = z.infer<typeof ModalFormBlockSchema>;
433
435
 
@@ -461,6 +463,12 @@ export const ModalSpecSchema = z.object({
461
463
  steps: ModalStepSchema.array().optional(),
462
464
  /** 자동 확인(리뷰) 스텝(G3) — steps 가 있을 때 마지막에 수집값 요약(라벨→값) 스텝을 붙인다. 기본 true. */
463
465
  review: z.boolean().optional(),
466
+ /**
467
+ * 작성 환경 자동 동봉(SCM-2 G6) — true 면 클라가 confirm 제출의 `UiActionContent.client` 에
468
+ * 플랫폼·버전 메타를 싣고, **무엇을 싣는지 폼 하단에 표시한다**(몰래 수집 금지 — 액션 이벤트는
469
+ * 방 멤버 전원이 읽는다). 이 필드를 모르는 옛 클라는 zod strip → 메타 없이 제출(수신측은 생략 처리).
470
+ */
471
+ diagnostics: z.boolean().optional(),
464
472
  /** 확인 버튼(있으면) — 누르면 confirm.action_id 를 발행하고 닫는다. */
465
473
  confirm: ModalConfirmSchema.optional(),
466
474
  /** 취소/닫기 라벨(기본 "Cancel"). */
@@ -500,6 +508,112 @@ export const ObjectMapSpecSchema = z.object({
500
508
  });
501
509
  export type ObjectMapSpec = z.infer<typeof ObjectMapSpecSchema>;
502
510
 
511
+ // --- 우측 셸 패널 기여 (ShellPanelTabContribution) — PANEL-SDK-1 ---
512
+ // SDK 앱이 우측 패널에 Files 와 동급의 **독립 탭**을 선언형으로 기여한다. 기존 PanelTabSchema 는
513
+ // PanelSpec *내부의 서브탭*이라 이름을 겹치지 않는다(티켓 §3.1). 배치(panel_tabs)와 콘텐츠(kind)를
514
+ // 분리한다: 파일 관리는 콘텐츠 종류 중 하나(assetBrowser)일 뿐, 탭 배치 API 가 아니다(잠금 결정 1).
515
+ // 호스트가 실제 탭 id 를 `app:<app>:<tab.id>` 로 네임스페이스해 코어 id(panel/files/preview:*)와
516
+ // 충돌할 수 없게 한다(client-web core/panelContributions.ts).
517
+
518
+ /** 앱당 panel_tabs 상한 — 스키마(max)와 인바운드 degrade(초과분 자르기)가 같은 값을 본다. */
519
+ export const MAX_PANEL_TAB_CONTRIBUTIONS = 8;
520
+ /** 앱당 preview_providers 상한 — 위와 같은 쌍. */
521
+ export const MAX_PREVIEW_PROVIDERS = 32;
522
+
523
+ /** 필수 파일 슬롯 — "이 종류의 파일이 최소 몇 개 있어야 한다"(레퍼런스/기획설계 등). */
524
+ export const AssetBrowserSlotSchema = z.object({
525
+ id: z.string().min(1),
526
+ label: z.string().min(1),
527
+ /** 허용 형식 — ".pdf"(확장자) 또는 "image/*"(MIME) 문자열. 업로드 input accept 와 같은 어휘. */
528
+ accept: z.string().array().optional(),
529
+ /** 충족으로 판정할 최소 개수(기본 1). */
530
+ min: z.number().int().min(0).optional(),
531
+ });
532
+ export type AssetBrowserSlot = z.infer<typeof AssetBrowserSlotSchema>;
533
+
534
+ /**
535
+ * 파일 브라우저 스펙 — 폴더/현재 버전/버전 이력을 **앱 네임스페이스 이벤트**로 연결한다(티켓 §6).
536
+ * mxc(바이트 주소)와 앱 도메인 메타데이터(asset/folder/version)를 분리한다(잠금 결정 7).
537
+ * 렌더러(AssetBrowserPanel)는 Phase D — 스키마/계약을 먼저 고정한다.
538
+ */
539
+ export const AssetBrowserSpecSchema = z.object({
540
+ title: z.string().optional(),
541
+ /** 데이터 이벤트 타입 — folder/current 는 state, version 은 timeline(append-only). */
542
+ events: z.object({
543
+ folder: z.string().min(1),
544
+ current: z.string().min(1),
545
+ version: z.string().min(1),
546
+ }),
547
+ /** 조작 액션 id(org.corp.ui.action 왕복) — 없으면 읽기 전용 브라우저. */
548
+ actions: z
549
+ .object({
550
+ upload: z.string().optional(),
551
+ createFolder: z.string().optional(),
552
+ rename: z.string().optional(),
553
+ })
554
+ .optional(),
555
+ requiredSlots: AssetBrowserSlotSchema.array().optional(),
556
+ });
557
+ export type AssetBrowserSpec = z.infer<typeof AssetBrowserSpecSchema>;
558
+
559
+ /**
560
+ * 기여 탭의 콘텐츠 — 닫힌 판별 유니온(잠금 결정 3: React/임의 CSS 주입 없음).
561
+ * - surface: 기존 닫힌 SurfaceBody 조합(PanelSpec)을 그대로 렌더(SurfaceRenderer 재사용).
562
+ * - assetBrowser: 파일 관리 명세(위). 미래 kind 는 dropUnknownSurfaceSections 가 탭 단위로 거른다.
563
+ */
564
+ export const ShellPanelContentSchema = z.discriminatedUnion("kind", [
565
+ z.object({ kind: z.literal("surface"), panel: PanelSpecSchema }),
566
+ AssetBrowserSpecSchema.extend({ kind: z.literal("assetBrowser") }),
567
+ ]);
568
+ export type ShellPanelContent = z.infer<typeof ShellPanelContentSchema>;
569
+
570
+ /** 우측 셸 패널 기여 탭 하나 — id 는 앱 내부 고유(호스트가 `app:<app>:` 접두사로 네임스페이스). */
571
+ export const ShellPanelTabContributionSchema = z.object({
572
+ id: z.string().min(1),
573
+ label: z.string().min(1),
574
+ /** 호스트 아이콘 집합(components/icons) 이름 — 미지 값은 중립 폴백(빈 원). */
575
+ icon: z.string().optional(),
576
+ /**
577
+ * lazy(기본) — 활성일 때만 마운트. keep-alive — **한 번 열린 뒤**에는 전환해도 살려 둔다
578
+ * (WorkspaceShell 의 기존 규칙 보존: 열지도 않은 탭을 미리 마운트하지 않는다).
579
+ */
580
+ lifecycle: z.enum(["lazy", "keep-alive"]).optional(),
581
+ content: ShellPanelContentSchema,
582
+ });
583
+ export type ShellPanelTabContribution = z.infer<typeof ShellPanelTabContributionSchema>;
584
+
585
+ // --- 동적 미리보기 제공자 (PreviewProviderSpec) — PANEL-SDK-1 ---
586
+ // 앱이 코어가 모르는 확장자/MIME 을 **호스트 내장 안전 렌더러**에 매핑한다(잠금 결정 4: 매칭과
587
+ // 렌더링 분리 — 임의 실행 코드는 0.4 의 별도 샌드박스 경로만). 매칭 규칙은 순수 함수
588
+ // resolvePreviewProvider(client-web core/previewRegistry.ts)가 고정한다: 코어 아는 형식 우선,
589
+ // 앱 간은 room.apps 순서, provider 안에서는 extension/MIME OR.
590
+
591
+ /** 호스트 내장 안전 renderer id — Phase C 에서 renderer registry 의 키가 된다. */
592
+ export const NativePreviewRendererIdSchema = z.enum([
593
+ "image",
594
+ "video",
595
+ "audio",
596
+ "pdf",
597
+ "text",
598
+ "markdown",
599
+ "html-sandbox",
600
+ ]);
601
+ export type NativePreviewRendererId = z.infer<typeof NativePreviewRendererIdSchema>;
602
+
603
+ /** 미리보기 제공자 하나 — 확장자/MIME 매칭 → 내장 renderer. */
604
+ export const PreviewProviderSpecSchema = z.object({
605
+ id: z.string().min(1),
606
+ match: z.object({
607
+ /** 점 포함 확장자(".dxf") 권장 — 클라가 소문자·점 포함으로 정규화해 매칭한다. */
608
+ extensions: z.string().array().optional(),
609
+ /** exact("text/plain") 또는 wildcard("image/*"). */
610
+ mimeTypes: z.string().array().optional(),
611
+ }),
612
+ /** 렌더러 — 0.3 은 native 만(닫힌 enum). widget/converter 는 0.4 별도 보안 경로(티켓 §5.4). */
613
+ renderer: z.object({ kind: z.literal("native"), id: NativePreviewRendererIdSchema }),
614
+ });
615
+ export type PreviewProviderSpec = z.infer<typeof PreviewProviderSpecSchema>;
616
+
503
617
  // --- 앱 서피스 와이어 이벤트 (org.corp.app.surface) — "서피스를 데이터로" ---
504
618
  // 클라에 팩을 컴파일해 넣는 대신(registerAppPack=클라 평면), 앱(App Service)이 셸 서피스(패널/헤더)를
505
619
  // 상태 이벤트로 룸에 실어 보낸다 → "Matrix App Service 만 추가해도" 회사별 UI 가 등록·렌더된다.
@@ -517,8 +631,13 @@ export const AppSurfaceContentSchema = z.object({
517
631
  display_name: z.string().optional(),
518
632
  /** 이 서피스가 다루는 엔티티 타입(표시/진단용). */
519
633
  entity_type: z.string().optional(),
520
- /** 우측 컨텍스트 패널(닫힌 SurfaceBody 조합). {{entity.path}} 바인딩은 클라가 resolveBindings 로 해석. */
521
- panel: PanelSpecSchema,
634
+ /**
635
+ * 우측 컨텍스트 패널(닫힌 SurfaceBody 조합) — PANEL-SDK-1 부터 **선택**. panel_tabs 만 기여하는
636
+ * 앱은 panel 없이도 유효한 서피스다. 이 필드를 모르는 구 클라에서는 "신규 탭만 가진 앱이 안 보임"
637
+ * 으로 축소될 뿐 기존 서피스는 그대로 동작한다(하위호환 UI 가 필요한 앱은 0.3 동안 sidebar 병행).
638
+ * {{entity.path}} 바인딩은 클라가 resolveBindings 로 해석.
639
+ */
640
+ panel: PanelSpecSchema.optional(),
522
641
  /**
523
642
  * 메인 영역 서피스(닫힌 SurfaceBody 조합) — 선택. 우측 panel 의 짝으로, 룸 본문(타임라인 대신)에
524
643
  * 탭으로 그릴 중앙 콘텐츠를 데이터로 기술한다. 없으면 이 앱은 사이드바 탭만 제공한다.
@@ -542,6 +661,13 @@ export const AppSurfaceContentSchema = z.object({
542
661
  objectMap: ObjectMapSpecSchema.optional(),
543
662
  /** 선언형 모달/시트(확인 다이얼로그) — 선택(AppPack.modals 동일). 같은 id 의 셸 액션이 연다. */
544
663
  modals: ModalSpecSchema.array().optional(),
664
+ /**
665
+ * 우측 셸 패널 기여 탭(PANEL-SDK-1) — Files 옆에 서는 독립 탭 목록. 순서 = 선언 순서.
666
+ * 이 필드를 모르는 옛 클라는 zod strip 으로 조용히 무시한다(기존 M3 필드와 같은 자세).
667
+ */
668
+ panel_tabs: ShellPanelTabContributionSchema.array().max(MAX_PANEL_TAB_CONTRIBUTIONS).optional(),
669
+ /** 동적 미리보기 제공자(PANEL-SDK-1) — 미지 확장자/MIME → 내장 renderer 매핑. 순서 = 선언 순서. */
670
+ preview_providers: PreviewProviderSpecSchema.array().max(MAX_PREVIEW_PROVIDERS).optional(),
545
671
  /**
546
672
  * 회사별 브랜드 토큰 오버라이드(--cob-color-* → 값). 클라가 룩(디자인 토큰)을 소유하되,
547
673
  * 테넌트는 토큰만 갈아끼워 자기 브랜드를 입힌다(임의 CSS 아님 = 닫힌 토큰 키). 클라가 inline CSS 변수로 적용.
@@ -601,11 +727,38 @@ export function dropUnknownSurfaceSections(raw: unknown): unknown {
601
727
  return out;
602
728
  });
603
729
  };
730
+ // SDK 기여 탭(PANEL-SDK-1)도 같은 자세 — ① kind:"surface" 탭의 내부 panel 은 섹션 단위로 거르고,
731
+ // ② 그래도 미지(미래 kind)거나 깨진 contribution 은 **그 탭만** 드롭한다(탭 하나 때문에 배열
732
+ // 전체 → 서피스 전체가 붕괴하지 않게). ③ 미래 스키마가 상한을 올려 보내도 옛 클라의 max 검증이
733
+ // 전체를 죽이지 않도록 상한으로 자른다(발신 빌더는 strict 라 정상 경로에선 안 걸린다).
734
+ const keepPanelTabs = (tabs: unknown): unknown => {
735
+ if (!Array.isArray(tabs)) return tabs;
736
+ return tabs
737
+ .map((t) => {
738
+ if (!t || typeof t !== "object") return t;
739
+ const tt = t as Record<string, unknown>;
740
+ const content = tt.content;
741
+ if (content && typeof content === "object" && (content as Record<string, unknown>).kind === "surface") {
742
+ const cc = content as Record<string, unknown>;
743
+ return { ...tt, content: { ...cc, panel: keepSections(cc.panel) } };
744
+ }
745
+ return t;
746
+ })
747
+ .filter((t) => ShellPanelTabContributionSchema.safeParse(t).success)
748
+ .slice(0, MAX_PANEL_TAB_CONTRIBUTIONS);
749
+ };
750
+ // 미리보기 제공자 — 미래 renderer kind("widget", 0.4)가 섞여 와도 provider 단위로만 거른다.
751
+ const keepPreviewProviders = (providers: unknown): unknown =>
752
+ Array.isArray(providers)
753
+ ? providers.filter((p) => PreviewProviderSpecSchema.safeParse(p).success).slice(0, MAX_PREVIEW_PROVIDERS)
754
+ : providers;
604
755
  return {
605
756
  ...c,
606
757
  ...(c.panel !== undefined ? { panel: keepSections(c.panel) } : {}),
607
758
  ...(c.main !== undefined ? { main: keepSections(c.main) } : {}),
608
759
  ...(c.modals !== undefined ? { modals: keepModalBlocks(c.modals) } : {}),
760
+ ...(c.panel_tabs !== undefined ? { panel_tabs: keepPanelTabs(c.panel_tabs) } : {}),
761
+ ...(c.preview_providers !== undefined ? { preview_providers: keepPreviewProviders(c.preview_providers) } : {}),
609
762
  };
610
763
  }
611
764