@underdogai/mesh-app-sdk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/action.d.ts +55 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +33 -0
- package/dist/action.js.map +1 -0
- package/dist/app.d.ts +126 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +214 -0
- package/dist/app.js.map +1 -0
- package/dist/builders.d.ts +77 -0
- package/dist/builders.d.ts.map +1 -0
- package/dist/builders.js +72 -0
- package/dist/builders.js.map +1 -0
- package/dist/host.d.ts +38 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +58 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +232 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +246 -0
- package/dist/runtime.js.map +1 -0
- package/dist/serve.d.ts +119 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +415 -0
- package/dist/serve.js.map +1 -0
- package/package.json +53 -0
- package/src/action.ts +74 -0
- package/src/app.ts +350 -0
- package/src/builders.ts +87 -0
- package/src/host.ts +87 -0
- package/src/index.ts +15 -0
- package/src/runtime.ts +462 -0
- package/src/serve.ts +520 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Underdog
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @underdogai/mesh-app-sdk
|
|
2
|
+
|
|
3
|
+
Matrix 채팅방에 붙는 앱을 **선언형으로** 만드는 SDK. 화면(카드·패널·컴포저·⌘K)과 동작(버튼·구독·호출)을 한 곳에서 정의하고, **봇 계정 토큰 하나로** 실행한다. 홈서버 관리자 권한이나 Application Service 등록 파일이 필요 없다.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i @underdogai/mesh-app-sdk zod
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 5줄 요약
|
|
10
|
+
|
|
11
|
+
- 앱이 보내는 건 전부 **데이터**(Matrix 이벤트)다 — 클라이언트에서 남의 코드가 실행되지 않는다.
|
|
12
|
+
- 화면은 방의 **상태 이벤트**로 저장된다 — 한 번 올리면 그 방의 모든 멤버에게 계속 보인다.
|
|
13
|
+
- 버튼·폼 제출·앱 간 호출은 전부 하나의 와이어(`org.corp.ui.action`)로 흐른다.
|
|
14
|
+
- 핸들러는 부작용을 직접 실행하지 않고 **효과(Effect)를 선언**한다 — 홈서버 없이 테스트할 수 있다.
|
|
15
|
+
- 실행 방식(1회 발행 / 봇 / App Service)은 앱 코드를 바꾸지 않고 고른다.
|
|
16
|
+
|
|
17
|
+
## 최소 예제
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { defineApp, defineAction, card, panel, serve } from "@underdogai/mesh-app-sdk";
|
|
22
|
+
|
|
23
|
+
const open = defineAction({
|
|
24
|
+
id: "helpdesk.open", // id 는 반드시 "<app>." 접두사 — 이게 라우팅 주소다
|
|
25
|
+
scope: "ticket:open",
|
|
26
|
+
values: z.object({}).passthrough(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const app = defineApp({
|
|
30
|
+
id: "helpdesk", app: "helpdesk", entityTypes: ["ticket"],
|
|
31
|
+
actions: [open],
|
|
32
|
+
handlers: {
|
|
33
|
+
[open.id]: (c) => c.send(card.build("hello", [card.text("눌렀습니다")], { title: "Helpdesk" })),
|
|
34
|
+
},
|
|
35
|
+
sidebar: panel.sections([panel.fields("t", "Tickets", [{ label: "Open", value: "3" }])]),
|
|
36
|
+
composer: {
|
|
37
|
+
placeholder: "Message or /ticket…",
|
|
38
|
+
commands: [{ trigger: "/ticket", label: "Open ticket", action_id: open.id }],
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await serve(app, { homeserver: "https://your-homeserver", accessToken: process.env.BOT_TOKEN });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
봇을 방에 초대하고 **권한 등급 50(중재자)** 을 주면 화면이 뜬다. 권한을 나중에 줘도 봇을 다시 켤 필요가 없다.
|
|
46
|
+
|
|
47
|
+
## 네 가지 통로
|
|
48
|
+
|
|
49
|
+
| 통로 | 뜻 |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `ctx.emit(type, content, { form })` | 사실을 발행 — `"event"`(조용) · `"message"`(카드) · `"state"`(방 상태) |
|
|
52
|
+
| `on: { [type]: handler }` | 남이 낸 이벤트를 구독해 반응 |
|
|
53
|
+
| `ctx.invoke(actionId, values)` | 다른 앱의 기능을 이름으로 호출 |
|
|
54
|
+
| `handlers: { [actionId]: handler }` | 내 기능이 불리면 실행 (내 것이 아니면 조용히 무시) |
|
|
55
|
+
|
|
56
|
+
## 실행 방식
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
await publish(app, { homeserver, accessToken, roomId }); // 1회 발행, 프로세스 없음
|
|
60
|
+
await serve(app, { homeserver, accessToken }); // 봇 상시 실행 (대부분 이걸로 충분)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
되돌릴 수 없는 액션(`destructive: true`)은 기본적으로 **누른 사람의 권한 등급 50 이상**을 요구한다. 자체 정책은 `serve(app, { gate })` 로 대체할 수 있고, 초대 수락 범위는 `acceptInvite` 로 좁힐 수 있다.
|
|
64
|
+
|
|
65
|
+
## 문서
|
|
66
|
+
|
|
67
|
+
전체 가이드 — 설치부터 화면 지도, 권한 모델, 보안 수칙까지: **https://chatops.theunderdog.ai/sdk-guide**
|
|
68
|
+
|
|
69
|
+
## 라이선스
|
|
70
|
+
|
|
71
|
+
MIT
|
package/dist/action.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 액션 정의 — 카드 버튼(UI)과 서버 핸들러(동작)를 "하나의 id/scope/values"로 묶는 컨트랙트.
|
|
3
|
+
*
|
|
4
|
+
* 지금까지 흩어져 있던 것 — 카드 빌더의 `action_id` 문자열 ↔ 핸들러의 `if (action_id === ...)` ↔
|
|
5
|
+
* policy 의 `ACTION_SCOPE` 하드코딩 맵 — 을 defineAction 한 곳으로 모은다. 결과:
|
|
6
|
+
* - 버튼의 action_id 가 정의에서 파생 → 카드와 핸들러가 컴파일타임에 연결(오타/리네임 안전).
|
|
7
|
+
* - scope/destructive 는 데이터로 서버 게이트(evaluateAction)에 주입 → 보안은 서버가 소유, SDK 는 데이터만 공급.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import type { Block, ButtonStyle } from "@underdogai/mesh-event-schemas";
|
|
11
|
+
/** UiCardContent 의 actions 블록 element 타입을 스키마에서 직접 파생(닫힌 집합과 100% 일치 보장). */
|
|
12
|
+
type ActionsBlock = Extract<Block, {
|
|
13
|
+
type: "actions";
|
|
14
|
+
}>;
|
|
15
|
+
export type ButtonElement = ActionsBlock["elements"][number];
|
|
16
|
+
export interface ActionButtonOpts {
|
|
17
|
+
style?: ButtonStyle;
|
|
18
|
+
/** true → 기본 확인 다이얼로그, 객체 → 커스텀 확인 문구. UI 게이트일 뿐(권한은 서버). */
|
|
19
|
+
confirm?: boolean | {
|
|
20
|
+
title: string;
|
|
21
|
+
text: string;
|
|
22
|
+
};
|
|
23
|
+
/** 버튼 고유 값(폼 입력 values 와 별개). */
|
|
24
|
+
value?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface AppAction<V extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
27
|
+
/** 안정적 action_id (org.corp.ui.action.action_id 로 회신된다). */
|
|
28
|
+
id: string;
|
|
29
|
+
/** 이 액션을 실행하려면 에이전트 capability 에 있어야 하는 scope(서버 게이트가 검사). */
|
|
30
|
+
scope: string;
|
|
31
|
+
/** 파괴적(되돌릴 수 없는) 여부 — 참이면 서버가 발신자 PL 게이트도 적용(HITL). */
|
|
32
|
+
destructive: boolean;
|
|
33
|
+
/** 폼 입력(values) 검증 스키마 — 핸들러에서 .parse(ctx.values) 로 타입 확보. */
|
|
34
|
+
values: V;
|
|
35
|
+
/**
|
|
36
|
+
* 이 액션의 핸들러가 `ctx.backend.call(tool)` 로 부를 수 있는 MCP 도구 화이트리스트(declaration = 데이터).
|
|
37
|
+
* 미선언(undefined/빈 배열)이면 이 액션 핸들러는 backend 도구를 *전혀* 못 부른다(fail-closed).
|
|
38
|
+
* confused-deputy 방어: 핸들러가 선언 밖 도구(예: delete_invoice)를 부르면 런타임이 거부한다(SDK 가 기계적 강제,
|
|
39
|
+
* 도구 구현·권한은 호스트가 소유 = GateFn 과 같은 의존성 역전). scope 가 "발신자 자격"이라면 tools 는 "앱 리소스 접근면".
|
|
40
|
+
*/
|
|
41
|
+
tools?: string[];
|
|
42
|
+
/** 이 액션을 부르는 버튼 element(action_id 자동 연결). card.actions([...]) 에 넣는다. */
|
|
43
|
+
button(text: string, opts?: ActionButtonOpts): ButtonElement;
|
|
44
|
+
}
|
|
45
|
+
/** 액션을 한 번 정의 → 카드 버튼과 서버 핸들러가 같은 id/scope/values 를 공유한다. */
|
|
46
|
+
export declare function defineAction<V extends z.ZodTypeAny = z.ZodTypeAny>(def: {
|
|
47
|
+
id: string;
|
|
48
|
+
scope: string;
|
|
49
|
+
destructive?: boolean;
|
|
50
|
+
values?: V;
|
|
51
|
+
/** 핸들러가 호출 가능한 MCP 도구 화이트리스트(미선언=backend 도구 호출 금지, fail-closed). */
|
|
52
|
+
tools?: string[];
|
|
53
|
+
}): AppAction<V>;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAEzE,6EAA6E;AAC7E,KAAK,YAAY,GAAG,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU;IAC9D,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAC;IACX,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,WAAW,EAAE,OAAO,CAAC;IACrB,8DAA8D;IAC9D,MAAM,EAAE,CAAC,CAAC;IACV;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,aAAa,CAAC;CAC9D;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,EAAE,GAAG,EAAE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,GAAG,SAAS,CAAC,CAAC,CAAC,CAsBf"}
|
package/dist/action.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 액션 정의 — 카드 버튼(UI)과 서버 핸들러(동작)를 "하나의 id/scope/values"로 묶는 컨트랙트.
|
|
3
|
+
*
|
|
4
|
+
* 지금까지 흩어져 있던 것 — 카드 빌더의 `action_id` 문자열 ↔ 핸들러의 `if (action_id === ...)` ↔
|
|
5
|
+
* policy 의 `ACTION_SCOPE` 하드코딩 맵 — 을 defineAction 한 곳으로 모은다. 결과:
|
|
6
|
+
* - 버튼의 action_id 가 정의에서 파생 → 카드와 핸들러가 컴파일타임에 연결(오타/리네임 안전).
|
|
7
|
+
* - scope/destructive 는 데이터로 서버 게이트(evaluateAction)에 주입 → 보안은 서버가 소유, SDK 는 데이터만 공급.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
/** 액션을 한 번 정의 → 카드 버튼과 서버 핸들러가 같은 id/scope/values 를 공유한다. */
|
|
11
|
+
export function defineAction(def) {
|
|
12
|
+
const values = (def.values ?? z.object({}).passthrough());
|
|
13
|
+
return {
|
|
14
|
+
id: def.id,
|
|
15
|
+
scope: def.scope,
|
|
16
|
+
destructive: def.destructive ?? false,
|
|
17
|
+
values,
|
|
18
|
+
...(def.tools ? { tools: def.tools } : {}),
|
|
19
|
+
button(text, opts) {
|
|
20
|
+
const confirm = opts?.confirm === true ? { title: text, text: "Are you sure?" } : opts?.confirm || undefined;
|
|
21
|
+
const el = {
|
|
22
|
+
type: "button",
|
|
23
|
+
action_id: def.id,
|
|
24
|
+
text,
|
|
25
|
+
...(opts?.style ? { style: opts.style } : {}),
|
|
26
|
+
...(confirm ? { confirm } : {}),
|
|
27
|
+
...(opts?.value ? { value: opts.value } : {}),
|
|
28
|
+
};
|
|
29
|
+
return el;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.js","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmCxB,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAwC,GAOnE;IACC,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAiB,CAAC;IAC1E,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,KAAK;QACrC,MAAM;QACN,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1C,MAAM,CAAC,IAAI,EAAE,IAAI;YACf,MAAM,OAAO,GACX,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,IAAI,SAAS,CAAC;YAC/F,MAAM,EAAE,GAAG;gBACT,IAAI,EAAE,QAAiB;gBACvB,SAAS,EAAE,GAAG,CAAC,EAAE;gBACjB,IAAI;gBACJ,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7C,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/B,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9C,CAAC;YACF,OAAO,EAAmB,CAAC;QAC7B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineApp — 한 "앱"(회사 ERP 등)을 한 폴더에서 선언한다.
|
|
3
|
+
*
|
|
4
|
+
* App 은 Plugin 의 상위집합이라, enabledPlugins(@chatops/app-plugins)에 한 줄 올리면 클라/AS/브리지가
|
|
5
|
+
* 같은 스키마를 등록한다(SEC-2 로드 경로 재사용 = "Matrix App Service 만 추가"의 봉합선).
|
|
6
|
+
*
|
|
7
|
+
* 한 앱이 묶는 삼각형:
|
|
8
|
+
* - events: 커스텀 이벤트 → 카드(데이터) 결합("어떤 UI가 어떤 이벤트에"). messageForEvent 로 AS가 전송.
|
|
9
|
+
* - actions/handlers: 카드 버튼 ↔ 서버 핸들러(동작). dispatchAction(runtime)이 게이트 후 실행.
|
|
10
|
+
* - on: 구독 선언("남이 낸 이벤트가 오면 반응"). dispatchEvent(runtime)가 라우팅(이벤트 드리븐 통로).
|
|
11
|
+
* - sidebar/header: 우측 셸 서피스(데이터). surfaceState 로 AS가 상태 이벤트(org.corp.app.surface)로 전송.
|
|
12
|
+
* + theme: 회사별 토큰 오버라이드(데이터). 전부 "기존 어휘 조합 = 데이터" → 클라 무변경.
|
|
13
|
+
*/
|
|
14
|
+
import type { z } from "zod";
|
|
15
|
+
import { buildUiMessageContent, type AgentDockSpec, type AppSurfaceContent, type CanvasSpec, type CommandPaletteSpec, type ComposerSpec, type HeaderSpec, type ModalSpec, type ObjectMapSpec, type PanelSpec, type Plugin, type Publisher, type UiCardContent } from "@underdogai/mesh-event-schemas";
|
|
16
|
+
import type { AppAction } from "./action.js";
|
|
17
|
+
import type { ActionHandler, EventHandler } from "./runtime.js";
|
|
18
|
+
export interface AppEventDef<S extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
19
|
+
/** 이 도메인 이벤트 content 스키마(레지스트리에 등록 → validateEvent/canPublish 가 인식). */
|
|
20
|
+
schema: S;
|
|
21
|
+
/** 이 이벤트가 들어오면 어떤 카드(기존 블록 조합)로 그릴지 — 이벤트 ↔ UI 결합. */
|
|
22
|
+
view: (content: z.infer<S>) => UiCardContent;
|
|
23
|
+
publishableBy?: Publisher[];
|
|
24
|
+
}
|
|
25
|
+
export interface AppDef {
|
|
26
|
+
/** 전역 고유 id(패키지 1개 = 1개). */
|
|
27
|
+
id: string;
|
|
28
|
+
/** room.binding/app.surface 의 app 키(작성자/도메인 식별자). */
|
|
29
|
+
app: string;
|
|
30
|
+
displayName?: string;
|
|
31
|
+
entityTypes: string[];
|
|
32
|
+
/**
|
|
33
|
+
* 코어 앱 여부 — true 면 이 앱의 액션은 *모든* 방에서 동작한다(room.binding 격리 면제). 에이전트 자신의
|
|
34
|
+
* 내장 능력(예: finance 의 invoice 승인)을 표현한다. false(기본)인 도메인 앱(acme/northwind 등)은
|
|
35
|
+
* 자기 방에 바인딩됐을 때만 동작한다(per-room 격리). 보안 게이트(scope/PL)는 코어 앱에도 그대로 적용된다.
|
|
36
|
+
* ⚠️ 신뢰 경계: core 는 격리를 면제하므로 *호스트가 신뢰하는* 1st-party 앱만 가져야 한다. 서드파티 로딩(G5)에선
|
|
37
|
+
* 앱이 self-declare 한 core 를 그대로 믿으면 안 되고, 호스트가 manifest 검수 단계에서 core 부여를 통제해야 한다.
|
|
38
|
+
*/
|
|
39
|
+
core?: boolean;
|
|
40
|
+
/** 커스텀 이벤트 → 카드 결합(이벤트 타입 → { schema, view }). */
|
|
41
|
+
events?: Record<string, AppEventDef>;
|
|
42
|
+
/**
|
|
43
|
+
* 구독 선언 — "이 이벤트 타입이 오면 반응한다"(이벤트 타입 → 핸들러). events.view 가 *렌더링*이라면
|
|
44
|
+
* on 은 *반응*이다. 남(다른 앱·브리지)의 타입을 들어도 되고, 여러 앱이 같은 타입을 함께 들어도 된다
|
|
45
|
+
* (이벤트 드리븐 — 주소 없음). 라우팅은 호스트가 dispatchEvent 로 한다. org.corp.ui.action 구독은
|
|
46
|
+
* 정의 시점에 거부한다 — 액션의 유일한 진입로는 게이트를 지나는 dispatchAction 이다(권한 우회 차단).
|
|
47
|
+
*/
|
|
48
|
+
on?: Record<string, EventHandler>;
|
|
49
|
+
/** 액션 컨트랙트(버튼 + scope). */
|
|
50
|
+
actions?: AppAction[];
|
|
51
|
+
/** action_id → 서버 핸들러(동작). actions 와 짝(서비스 면). */
|
|
52
|
+
handlers?: Record<string, ActionHandler>;
|
|
53
|
+
/** 우측 셸 패널(데이터). surfaceState 로 상태 이벤트화. */
|
|
54
|
+
sidebar?: PanelSpec;
|
|
55
|
+
/** 메인 영역 서피스(데이터, 선택) — 룸 본문에 탭으로 그릴 중앙 콘텐츠. sidebar 와 함께 발행된다. */
|
|
56
|
+
main?: PanelSpec;
|
|
57
|
+
/** 룸 헤더(데이터, 선택). */
|
|
58
|
+
header?: HeaderSpec;
|
|
59
|
+
/** 이벤트 캔버스(보드). */
|
|
60
|
+
canvas?: CanvasSpec;
|
|
61
|
+
/** 우측 에이전트 도크. */
|
|
62
|
+
dock?: AgentDockSpec;
|
|
63
|
+
/** 컴포저(슬래시 명령·퀵액션·@엔티티 자동완성). */
|
|
64
|
+
composer?: ComposerSpec;
|
|
65
|
+
/** ⌘K 커맨드 팔레트 엔트리. */
|
|
66
|
+
commandPalette?: CommandPaletteSpec;
|
|
67
|
+
/** 객체 맵(엔티티 관계 그래프). */
|
|
68
|
+
objectMap?: ObjectMapSpec;
|
|
69
|
+
/** 선언형 모달/시트(확인 다이얼로그) — 같은 id 의 셸 액션이 열고, 확인 시 confirm.action_id 발행. */
|
|
70
|
+
modals?: ModalSpec[];
|
|
71
|
+
/** 회사별 브랜드 토큰 오버라이드(데이터). */
|
|
72
|
+
theme?: Record<string, string>;
|
|
73
|
+
/**
|
|
74
|
+
* 핸들러 지연 등록(런타임 경계 물리화) — true 면 defineApp 은 핸들러 없이 App 을 만들고, 파괴적-액션-핸들러
|
|
75
|
+
* 링크검사를 미룬다. 호스트(AS)가 부팅 시 attachHandlers(app, handlers) 로 *service 서브패스*의 핸들러를 붙이며
|
|
76
|
+
* 그때 전체 링크검사를 한다. 효과: 핸들러(동작) 코드가 클라 번들에 실리지 않는다(클라는 contract/surface 만 로드).
|
|
77
|
+
*/
|
|
78
|
+
deferHandlers?: boolean;
|
|
79
|
+
}
|
|
80
|
+
export interface App extends Plugin {
|
|
81
|
+
actions: Map<string, AppAction>;
|
|
82
|
+
handlers: Map<string, ActionHandler>;
|
|
83
|
+
events: Record<string, AppEventDef>;
|
|
84
|
+
/** 구독 선언(이벤트 타입 → 핸들러) — AppDef.on. 호스트가 dispatchEvent 로 라우팅한다. */
|
|
85
|
+
on: Map<string, EventHandler>;
|
|
86
|
+
/** 코어 앱(모든 방에서 동작, room.binding 격리 면제) 여부 — AppDef.core. 호스트 라우터가 격리 검사 때 본다. */
|
|
87
|
+
core: boolean;
|
|
88
|
+
/** 도메인 이벤트 content → 보낼 메시지(데이터, org.corp.ui 카드 포함). 미등록/검증실패 시 null. */
|
|
89
|
+
messageForEvent(type: string, content: unknown): ReturnType<typeof buildUiMessageContent> | null;
|
|
90
|
+
/** 우측 셸 서피스를 상태 이벤트 content(데이터)로. sidebar 없으면 null. */
|
|
91
|
+
surfaceState(): AppSurfaceContent | null;
|
|
92
|
+
}
|
|
93
|
+
export declare function defineApp(def: AppDef): App;
|
|
94
|
+
/**
|
|
95
|
+
* service 핸들러를 (deferHandlers 로 만든) App 에 부착한다 — 런타임 경계 물리화의 호스트 측 절반.
|
|
96
|
+
* 클라는 contract/surface(핸들러 0)만 로드해 App 을 만들고, AS 만 `@chatops/app-<x>/service` 의 핸들러를 import 해
|
|
97
|
+
* 부팅 시 이걸 호출한다 → 핸들러(동작) 코드가 클라 번들에 실리지 않는다. 붙일 때 전체 링크검사를 수행한다(fail-fast).
|
|
98
|
+
* 같은 App 싱글톤의 handlers Map 을 채우므로 actionIndex/appsByName 가 참조하던 App 에 즉시 반영된다.
|
|
99
|
+
*/
|
|
100
|
+
export declare function attachHandlers(app: App, handlers: Record<string, ActionHandler>): App;
|
|
101
|
+
/**
|
|
102
|
+
* 레거시 플러그인(forgejo/ops 등 = AppPack/템플릿으로 카드를 그리지만 핸들러가 없던 것)에 액션/핸들러를 부착해
|
|
103
|
+
* App(=actionIndex 가 픽업하는 상위집합)으로 증강한다. 플러그인의 register()(AppPack/템플릿/이벤트 등록)는
|
|
104
|
+
* 그대로 보존하므로 풍부한 서피스를 잃지 않는다 — defineApp 전체 전환 없이 "카드 버튼이 핸들러로 왕복"만 켠다.
|
|
105
|
+
*
|
|
106
|
+
* 코어 변경 0: enabledPlugins 의 원소를 plain plugin → withActions(plugin, …) 로 바꾸면 그 도메인의 버튼이 살아난다.
|
|
107
|
+
* 도메인 앱이므로 core=false(방의 room.binding 에 바인딩됐을 때만 동작) — 보안 게이트는 그대로 서버.
|
|
108
|
+
* App.surface(서피스-as-데이터)는 발행하지 않는다(레거시는 AppPack 클라 평면으로 렌더) → surfaceState=null.
|
|
109
|
+
*/
|
|
110
|
+
export declare function withActions(plugin: Plugin, opts: {
|
|
111
|
+
actions: AppAction[];
|
|
112
|
+
handlers: Record<string, ActionHandler>;
|
|
113
|
+
}): App;
|
|
114
|
+
/** Plugin 이 App(=defineApp 산출물)인지 판정 — 액션/핸들러/이벤트를 가진 상위집합. 라이브 AS 가 enabledPlugins 에서 App 만 추릴 때 쓴다. */
|
|
115
|
+
export declare function isApp(p: Plugin): p is App;
|
|
116
|
+
/** Plugin 목록(클라/AS 공통 enabledPlugins)에서 App 만 추린다. */
|
|
117
|
+
export declare function enabledApps(plugins: Plugin[]): App[];
|
|
118
|
+
/**
|
|
119
|
+
* action_id → 소유 App 인덱스 — 라이브 AS 가 들어온 org.corp.ui.action 을 어느 App 의 핸들러로 보낼지 결정한다
|
|
120
|
+
* (이게 onUiAction 을 도메인 하드코딩 대신 제너릭 dispatchAction 루프로 만드는 빠진 이음새였다).
|
|
121
|
+
* 충돌(같은 action_id 를 두 App 이 소유)은 부팅 시 즉시 throw(fail-fast) — registry 의 prefix-shadow
|
|
122
|
+
* 가드와 같은 보수적 거부 정책(조용한 first-writer-wins 금지). action_id 는 App 간 고유해야 하며,
|
|
123
|
+
* 앱은 자기 네임스페이스로 두는 게 안전하다.
|
|
124
|
+
*/
|
|
125
|
+
export declare function buildActionIndex(apps: App[]): Map<string, App>;
|
|
126
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAIL,qBAAqB,EAErB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,MAAM,EAEX,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU;IAChE,wEAAwE;IACxE,MAAM,EAAE,CAAC,CAAC;IACV,sDAAsD;IACtD,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,aAAa,CAAC;IAC7C,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,MAAM;IACrB,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC;;;;;OAKG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAClC,2BAA2B;IAC3B,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,4CAA4C;IAC5C,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,mEAAmE;IACnE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,qBAAqB;IACrB,MAAM,CAAC,EAAE,UAAU,CAAC;IAEpB,mBAAmB;IACnB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,kBAAkB;IAClB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,sBAAsB;IACtB,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,wBAAwB;IACxB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,yEAAyE;IACzE,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,GAAI,SAAQ,MAAM;IACjC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,mEAAmE;IACnE,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9B,iFAAiF;IACjF,IAAI,EAAE,OAAO,CAAC;IACd,yEAAyE;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,GAAG,IAAI,CAAC;IACjG,wDAAwD;IACxD,YAAY,IAAI,iBAAiB,GAAG,IAAI,CAAC;CAC1C;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CA6F1C;AA2DD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,GAAG,CAIrF;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;CAAE,GACtE,GAAG,CAuBL;AAED,yGAAyG;AACzG,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,GAAG,CAGzC;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAG,EAAE,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAc9D"}
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { EventType, NS, buildAppSurface, buildUiMessageContent, registerEventEntry, } from "@underdogai/mesh-event-schemas";
|
|
2
|
+
export function defineApp(def) {
|
|
3
|
+
const actions = new Map();
|
|
4
|
+
for (const a of def.actions ?? [])
|
|
5
|
+
actions.set(a.id, a);
|
|
6
|
+
const handlers = new Map();
|
|
7
|
+
for (const [id, h] of Object.entries(def.handlers ?? {}))
|
|
8
|
+
handlers.set(id, h);
|
|
9
|
+
const events = def.events ?? {};
|
|
10
|
+
const on = new Map();
|
|
11
|
+
for (const [type, h] of Object.entries(def.on ?? {}))
|
|
12
|
+
on.set(type, h);
|
|
13
|
+
// ui.action 구독 금지(fail-fast) — 액션은 권한 게이트를 지나는 dispatchAction 만이 진입로다.
|
|
14
|
+
// on 으로 받으면 scope/PL/발행자격 검사를 전부 우회하게 되므로 정의 시점에 거부한다.
|
|
15
|
+
if (on.has(EventType.UiAction)) {
|
|
16
|
+
throw new Error(`[app-sdk] 앱 '${def.app}': '${EventType.UiAction}' 은 on 으로 구독할 수 없습니다 — ` +
|
|
17
|
+
`액션 수신은 actions/handlers(권한 게이트 경유)로 선언하세요.`);
|
|
18
|
+
}
|
|
19
|
+
// 액션 id 네임스페이스 강제 — 핸들러 유무와 무관하게 정의 시점에 항상 검사한다(deferHandlers 여도).
|
|
20
|
+
assertActionNamespace(def.app, actions);
|
|
21
|
+
// 부팅 링크검사(fail-fast) — 흩어진 버튼/핸들러/스코프 연결의 정합성을 정의 시점에 확인한다.
|
|
22
|
+
// deferHandlers(런타임 경계 분리)면 핸들러는 나중에 attachHandlers 가 붙이며 그때 검사한다 → 여기선 건너뛴다.
|
|
23
|
+
if (!def.deferHandlers)
|
|
24
|
+
assertActionHandlerLinks(def.app, actions, handlers);
|
|
25
|
+
// 앱은 자기 네임스페이스(org.corp.<app>.) 안에서만 이벤트를 등록할 수 있다(소유 강제).
|
|
26
|
+
const nsPrefix = `${NS}.${def.app}.`;
|
|
27
|
+
// Plugin.register — 부팅 시 1회(registerPlugin 가 plugin.id 로 멱등 가드). 도메인 이벤트 스키마를 레지스트리에 등록(클라/AS 공통).
|
|
28
|
+
const register = () => {
|
|
29
|
+
for (const [type, ev] of Object.entries(events)) {
|
|
30
|
+
if (!type.startsWith(nsPrefix)) {
|
|
31
|
+
throw new Error(`[app-sdk] 앱 '${def.app}': 이벤트 타입 '${type}' 가 앱 네임스페이스 '${nsPrefix}' 밖입니다 — 남의 네임스페이스 등록 금지.`);
|
|
32
|
+
}
|
|
33
|
+
// register 는 멱등 가드 뒤라 1회만 호출되므로, false 는 *진짜 충돌*(다른 앱이 같은 타입/접두사 섀도잉)이다 → throw.
|
|
34
|
+
const ok = registerEventEntry({
|
|
35
|
+
type,
|
|
36
|
+
kind: "message",
|
|
37
|
+
schema: ev.schema,
|
|
38
|
+
publishableBy: ev.publishableBy ?? ["bridge", "agent"],
|
|
39
|
+
description: `${def.app} 앱 도메인 이벤트`,
|
|
40
|
+
});
|
|
41
|
+
if (!ok) {
|
|
42
|
+
throw new Error(`[app-sdk] 앱 '${def.app}': 이벤트 타입 '${type}' 등록 실패(이미 등록됨/접두사 충돌) — 타입 충돌을 확인하세요.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 서피스는 데이터(org.corp.app.surface 상태 이벤트)로 흐르므로 registerAppPack(클라 평면) 불필요.
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
id: def.id,
|
|
49
|
+
app: def.app,
|
|
50
|
+
displayName: def.displayName,
|
|
51
|
+
entityTypes: def.entityTypes,
|
|
52
|
+
core: def.core ?? false,
|
|
53
|
+
provides: { events: Object.keys(events) },
|
|
54
|
+
capabilities: deriveCapabilities(actions),
|
|
55
|
+
register,
|
|
56
|
+
actions,
|
|
57
|
+
handlers,
|
|
58
|
+
events,
|
|
59
|
+
on,
|
|
60
|
+
messageForEvent(type, content) {
|
|
61
|
+
const ev = events[type];
|
|
62
|
+
if (!ev)
|
|
63
|
+
return null;
|
|
64
|
+
const parsed = ev.schema.safeParse(content);
|
|
65
|
+
if (!parsed.success)
|
|
66
|
+
return null;
|
|
67
|
+
return buildUiMessageContent(ev.view(parsed.data));
|
|
68
|
+
},
|
|
69
|
+
surfaceState() {
|
|
70
|
+
// panel(=sidebar)은 필수 와이어 필드라 sidebar 없으면 서피스를 발행하지 않는다.
|
|
71
|
+
// main 은 sidebar 와 함께만 실어 보낸다(메인 영역 탭 콘텐츠). main 만 있는 앱은 v1 미지원.
|
|
72
|
+
if (!def.sidebar)
|
|
73
|
+
return null;
|
|
74
|
+
return buildAppSurface({
|
|
75
|
+
app: def.app,
|
|
76
|
+
display_name: def.displayName,
|
|
77
|
+
entity_type: def.entityTypes[0],
|
|
78
|
+
panel: def.sidebar,
|
|
79
|
+
main: def.main,
|
|
80
|
+
header: def.header,
|
|
81
|
+
canvas: def.canvas,
|
|
82
|
+
dock: def.dock,
|
|
83
|
+
composer: def.composer,
|
|
84
|
+
commandPalette: def.commandPalette,
|
|
85
|
+
objectMap: def.objectMap,
|
|
86
|
+
modals: def.modals,
|
|
87
|
+
theme: def.theme,
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 액션↔핸들러 부팅 링크검사(fail-fast). defineApp 과 withActions 가 공유한다.
|
|
94
|
+
* (1) 핸들러 키가 액션에 없으면 = 오타/유령 핸들러 → throw(게이트는 통과하나 효과 0개로 status:"ran" 인데
|
|
95
|
+
* 아무 일도 안 일어나는 조용한 함정 차단). (2) 파괴적 액션에 핸들러 없으면 = 게이트만 통과하고 효과 없음 → throw.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* 액션 id 네임스페이스 강제(부팅 fail-fast) — 액션 id 는 `<app>.` 으로 시작해야 한다.
|
|
99
|
+
*
|
|
100
|
+
* 이벤트 타입에 이미 적용하던 소유 규칙(register 의 nsPrefix 검사)을 액션에도 똑같이 건다. action_id 는
|
|
101
|
+
* App 간 전역 고유해야 하고(buildActionIndex 가 충돌을 throw), 코어 프리미티브(approve/reject/cancel/send…)
|
|
102
|
+
* 와도 겹치면 안 되는데 — 평문 id 는 앱이 늘수록 충돌이 확정적이다(실제로 forgejo 의 PR 승인이 코어 invoice
|
|
103
|
+
* 의 "approve" 와 부딪혀 한 차례 우회한 전례가 있다). 소유자를 id 에 박아 구조적으로 막는다.
|
|
104
|
+
*/
|
|
105
|
+
function assertActionNamespace(label, actions) {
|
|
106
|
+
const prefix = `${label}.`;
|
|
107
|
+
for (const id of actions.keys()) {
|
|
108
|
+
if (!id.startsWith(prefix)) {
|
|
109
|
+
throw new Error(`[app-sdk] 앱 '${label}': 액션 id '${id}' 가 앱 네임스페이스 '${prefix}' 밖입니다 — ` +
|
|
110
|
+
`'${prefix}${id}' 처럼 앱 키를 접두사로 두세요(코어 프리미티브·타 앱과의 action_id 충돌 방지).`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* App 의 액션에서 capability 표면(scope/backendTools)을 자동 도출한다 — manifest.capabilities 로 게시되며,
|
|
116
|
+
* 코드(액션)와 선언이 한 출처라 어긋나지 않는다(서드파티 로딩 감사용 신분증). 액션 0개면 undefined.
|
|
117
|
+
*/
|
|
118
|
+
function deriveCapabilities(actions) {
|
|
119
|
+
const scopes = [...new Set([...actions.values()].map((a) => a.scope))];
|
|
120
|
+
const backendTools = [...new Set([...actions.values()].flatMap((a) => a.tools ?? []))];
|
|
121
|
+
if (!scopes.length && !backendTools.length)
|
|
122
|
+
return undefined;
|
|
123
|
+
return { ...(scopes.length ? { scopes } : {}), ...(backendTools.length ? { backendTools } : {}) };
|
|
124
|
+
}
|
|
125
|
+
function assertActionHandlerLinks(label, actions, handlers) {
|
|
126
|
+
for (const id of handlers.keys()) {
|
|
127
|
+
if (!actions.has(id)) {
|
|
128
|
+
throw new Error(`[app-sdk] 앱 '${label}': 핸들러 '${id}' 에 대응하는 액션이 없습니다(오타 가능). actions 에 정의하거나 핸들러 키를 고치세요.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const a of actions.values()) {
|
|
132
|
+
if (a.destructive && !handlers.has(a.id)) {
|
|
133
|
+
throw new Error(`[app-sdk] 앱 '${label}': 파괴적 액션 '${a.id}' 에 핸들러가 없습니다 — 핸들러를 정의하거나 destructive 를 내리세요.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* service 핸들러를 (deferHandlers 로 만든) App 에 부착한다 — 런타임 경계 물리화의 호스트 측 절반.
|
|
139
|
+
* 클라는 contract/surface(핸들러 0)만 로드해 App 을 만들고, AS 만 `@chatops/app-<x>/service` 의 핸들러를 import 해
|
|
140
|
+
* 부팅 시 이걸 호출한다 → 핸들러(동작) 코드가 클라 번들에 실리지 않는다. 붙일 때 전체 링크검사를 수행한다(fail-fast).
|
|
141
|
+
* 같은 App 싱글톤의 handlers Map 을 채우므로 actionIndex/appsByName 가 참조하던 App 에 즉시 반영된다.
|
|
142
|
+
*/
|
|
143
|
+
export function attachHandlers(app, handlers) {
|
|
144
|
+
for (const [id, h] of Object.entries(handlers))
|
|
145
|
+
app.handlers.set(id, h);
|
|
146
|
+
assertActionHandlerLinks(app.app, app.actions, app.handlers);
|
|
147
|
+
return app;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 레거시 플러그인(forgejo/ops 등 = AppPack/템플릿으로 카드를 그리지만 핸들러가 없던 것)에 액션/핸들러를 부착해
|
|
151
|
+
* App(=actionIndex 가 픽업하는 상위집합)으로 증강한다. 플러그인의 register()(AppPack/템플릿/이벤트 등록)는
|
|
152
|
+
* 그대로 보존하므로 풍부한 서피스를 잃지 않는다 — defineApp 전체 전환 없이 "카드 버튼이 핸들러로 왕복"만 켠다.
|
|
153
|
+
*
|
|
154
|
+
* 코어 변경 0: enabledPlugins 의 원소를 plain plugin → withActions(plugin, …) 로 바꾸면 그 도메인의 버튼이 살아난다.
|
|
155
|
+
* 도메인 앱이므로 core=false(방의 room.binding 에 바인딩됐을 때만 동작) — 보안 게이트는 그대로 서버.
|
|
156
|
+
* App.surface(서피스-as-데이터)는 발행하지 않는다(레거시는 AppPack 클라 평면으로 렌더) → surfaceState=null.
|
|
157
|
+
*/
|
|
158
|
+
export function withActions(plugin, opts) {
|
|
159
|
+
const app = plugin.app ?? plugin.id;
|
|
160
|
+
const actions = new Map();
|
|
161
|
+
for (const a of opts.actions)
|
|
162
|
+
actions.set(a.id, a);
|
|
163
|
+
const handlers = new Map();
|
|
164
|
+
for (const [id, h] of Object.entries(opts.handlers))
|
|
165
|
+
handlers.set(id, h);
|
|
166
|
+
assertActionNamespace(app, actions);
|
|
167
|
+
assertActionHandlerLinks(app, actions, handlers);
|
|
168
|
+
return {
|
|
169
|
+
...plugin,
|
|
170
|
+
app,
|
|
171
|
+
core: false,
|
|
172
|
+
// 레거시 manifest 의 capabilities 를 액션에서 도출한 값으로 덮어쓴다(withActions 로 부착한 액션의 실제 표면).
|
|
173
|
+
capabilities: deriveCapabilities(actions),
|
|
174
|
+
actions,
|
|
175
|
+
handlers,
|
|
176
|
+
events: {},
|
|
177
|
+
on: new Map(),
|
|
178
|
+
// 레거시 플러그인은 App.messageForEvent(이벤트→카드) 대신 자기 toCard/템플릿으로 렌더하고,
|
|
179
|
+
// 서피스는 AppPack(클라 평면)으로 등록한다 → App 의 데이터 평면 두 메서드는 비활성(null).
|
|
180
|
+
messageForEvent: () => null,
|
|
181
|
+
surfaceState: () => null,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/** Plugin 이 App(=defineApp 산출물)인지 판정 — 액션/핸들러/이벤트를 가진 상위집합. 라이브 AS 가 enabledPlugins 에서 App 만 추릴 때 쓴다. */
|
|
185
|
+
export function isApp(p) {
|
|
186
|
+
const a = p;
|
|
187
|
+
return a.actions instanceof Map && a.handlers instanceof Map && typeof a.messageForEvent === "function";
|
|
188
|
+
}
|
|
189
|
+
/** Plugin 목록(클라/AS 공통 enabledPlugins)에서 App 만 추린다. */
|
|
190
|
+
export function enabledApps(plugins) {
|
|
191
|
+
return plugins.filter(isApp);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* action_id → 소유 App 인덱스 — 라이브 AS 가 들어온 org.corp.ui.action 을 어느 App 의 핸들러로 보낼지 결정한다
|
|
195
|
+
* (이게 onUiAction 을 도메인 하드코딩 대신 제너릭 dispatchAction 루프로 만드는 빠진 이음새였다).
|
|
196
|
+
* 충돌(같은 action_id 를 두 App 이 소유)은 부팅 시 즉시 throw(fail-fast) — registry 의 prefix-shadow
|
|
197
|
+
* 가드와 같은 보수적 거부 정책(조용한 first-writer-wins 금지). action_id 는 App 간 고유해야 하며,
|
|
198
|
+
* 앱은 자기 네임스페이스로 두는 게 안전하다.
|
|
199
|
+
*/
|
|
200
|
+
export function buildActionIndex(apps) {
|
|
201
|
+
const index = new Map();
|
|
202
|
+
for (const app of apps) {
|
|
203
|
+
for (const id of app.actions.keys()) {
|
|
204
|
+
const owner = index.get(id);
|
|
205
|
+
if (owner && owner !== app) {
|
|
206
|
+
throw new Error(`[app-sdk] action_id 충돌: '${id}' 를 앱 '${owner.app}' 와 '${app.app}' 가 모두 소유합니다. action_id 는 App 간 고유해야 합니다(앱 네임스페이스 권장).`);
|
|
207
|
+
}
|
|
208
|
+
if (!owner)
|
|
209
|
+
index.set(id, app);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return index;
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 카드/패널 빌더 — "이미 있는 닫힌 블록/바디"를 조합해 데이터(UiCardContent / PanelSpec)를 만든다.
|
|
3
|
+
*
|
|
4
|
+
* 핵심: 새 블록을 만들지 않는다(코어 어휘만 조합). 그래서 결과가 "데이터"로 남고 — App Service 가
|
|
5
|
+
* 그대로 와이어에 실어 보내면 클라가 가진 렌더러로 그린다(클라 무변경 = "AS만 추가해도 UI 등록").
|
|
6
|
+
*/
|
|
7
|
+
import { type Block, type PanelSpec, type SurfaceSection, type UiCardContent } from "@underdogai/mesh-event-schemas";
|
|
8
|
+
import type { ButtonElement } from "./action.js";
|
|
9
|
+
export interface CardOpts {
|
|
10
|
+
title?: string;
|
|
11
|
+
fallback?: string;
|
|
12
|
+
state?: UiCardContent["state"];
|
|
13
|
+
context?: UiCardContent["context"];
|
|
14
|
+
}
|
|
15
|
+
/** 타임라인 카드(UiCardContent) 빌더 — 기존 블록 조합 → 검증된 데이터. */
|
|
16
|
+
export declare const card: {
|
|
17
|
+
/** 닫힌 블록 트리로 카드를 구성(반환 즉시 zod 검증된 UiCardContent). */
|
|
18
|
+
build(card_id: string, blocks: Block[], opts?: CardOpts): UiCardContent;
|
|
19
|
+
fields(items: {
|
|
20
|
+
label: string;
|
|
21
|
+
value: string;
|
|
22
|
+
short?: boolean;
|
|
23
|
+
}[]): Block;
|
|
24
|
+
text(text: string): Block;
|
|
25
|
+
actions(elements: ButtonElement[]): Block;
|
|
26
|
+
/** 텍스트/숫자/날짜 입력 — input 종류는 "text"(기본)·"textarea"·"number"·"date"·"datetime"·"time". */
|
|
27
|
+
input(name: string, opts?: {
|
|
28
|
+
label?: string;
|
|
29
|
+
input?: "text" | "textarea" | "number" | "date" | "datetime" | "time";
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
required?: boolean;
|
|
32
|
+
value?: string;
|
|
33
|
+
}): Block;
|
|
34
|
+
/** 드롭다운 선택(단일/다중). */
|
|
35
|
+
select(name: string, options: {
|
|
36
|
+
value: string;
|
|
37
|
+
label: string;
|
|
38
|
+
}[], opts?: {
|
|
39
|
+
label?: string;
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
value?: string;
|
|
42
|
+
multi?: boolean;
|
|
43
|
+
}): Block;
|
|
44
|
+
/** 라디오 그룹(옵션이 펼쳐져 보이는 단일 선택). */
|
|
45
|
+
radio(name: string, options: {
|
|
46
|
+
value: string;
|
|
47
|
+
label: string;
|
|
48
|
+
}[], opts?: {
|
|
49
|
+
label?: string;
|
|
50
|
+
value?: string;
|
|
51
|
+
}): Block;
|
|
52
|
+
/** 체크박스(불리언 수집). */
|
|
53
|
+
checkbox(name: string, label: string, opts?: {
|
|
54
|
+
checked?: boolean;
|
|
55
|
+
}): Block;
|
|
56
|
+
/** 토글 스위치(불리언 수집, on/off 어포던스). */
|
|
57
|
+
toggle(name: string, label: string, opts?: {
|
|
58
|
+
checked?: boolean;
|
|
59
|
+
onText?: string;
|
|
60
|
+
offText?: string;
|
|
61
|
+
}): Block;
|
|
62
|
+
};
|
|
63
|
+
/** 우측 셸 패널(PanelSpec) 빌더 — 닫힌 SurfaceBody 조합. value 문자열의 {{entity.path}} 는 클라가 바인딩. */
|
|
64
|
+
export declare const panel: {
|
|
65
|
+
sections(sections: SurfaceSection[]): PanelSpec;
|
|
66
|
+
fields(id: string, title: string, items: {
|
|
67
|
+
label: string;
|
|
68
|
+
value: string;
|
|
69
|
+
}[]): SurfaceSection;
|
|
70
|
+
list(id: string, title: string, items: {
|
|
71
|
+
primary: string;
|
|
72
|
+
secondary?: string;
|
|
73
|
+
}[]): SurfaceSection;
|
|
74
|
+
memo(id: string, title: string, summary: string, updates?: string[]): SurfaceSection;
|
|
75
|
+
card(id: string, title: string, embedded: UiCardContent): SurfaceSection;
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=builders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACpC;AAED,sDAAsD;AACtD,eAAO,MAAM,IAAI;IACf,qDAAqD;mBACtC,MAAM,UAAU,KAAK,EAAE,SAAQ,QAAQ,GAAQ,aAAa;kBAU7D;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,GAAG,KAAK;eAG9D,MAAM,GAAG,KAAK;sBAGP,aAAa,EAAE,GAAG,KAAK;IAMzC,wFAAwF;gBAC5E,MAAM,SAAQ;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAQ,KAAK;IAG1L,sBAAsB;iBACT,MAAM,WAAW;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,SAAQ;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAQ,KAAK;IAG9J,iCAAiC;gBACrB,MAAM,WAAW;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,SAAQ;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAQ,KAAK;IAGtH,oBAAoB;mBACL,MAAM,SAAS,MAAM,SAAQ;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAQ,KAAK;IAG9E,mCAAmC;iBACtB,MAAM,SAAS,MAAM,SAAQ;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAQ,KAAK;CAGhH,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,KAAK;uBACG,cAAc,EAAE,GAAG,SAAS;eAGpC,MAAM,SAAS,MAAM,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,cAAc;aAGnF,MAAM,SAAS,MAAM,SAAS;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,cAAc;aAGxF,MAAM,SAAS,MAAM,WAAW,MAAM,YAAY,MAAM,EAAE,GAAG,cAAc;aAG3E,MAAM,SAAS,MAAM,YAAY,aAAa,GAAG,cAAc;CAGzE,CAAC"}
|