@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.
@@ -0,0 +1,246 @@
1
+ /**
2
+ * 액션 라우터 — 들어온 org.corp.ui.action 을 (멱등은 호스트가) → 발행자격(SEC-3) → 레지스트리 검증
3
+ * → 권한 게이트 → 핸들러 실행(효과 수집) 으로 흘린다.
4
+ *
5
+ * 보안 게이트(evaluateAction)는 SDK 가 소유하지 않고 **호스트가 주입**한다(의존성 역전) — SDK 는
6
+ * "어떤 액션이 어떤 scope/destructive 인지"만 게이트에 데이터로 넘긴다(보안의 권위는 서버 policy 에 그대로).
7
+ *
8
+ * 핸들러는 부작용을 직접 실행하지 않고 "효과(Effect)"를 선언한다 → 실 AS 는 matrix client 로 적용,
9
+ * 증명 스크립트는 효과 배열을 검사한다(같은 SDK 코드를 양쪽에서 검증 = 테스트성).
10
+ */
11
+ import { EventType, NS, buildUiActionContent, buildUiMessageContent, buildUiMessageEdit, canPublish, classifySender, validateEvent, } from "@underdogai/mesh-event-schemas";
12
+ /** 액션 한 건을 라우팅한다. 게이트는 호스트가 주입(보안 권위는 서버). 효과는 호스트가 적용. */
13
+ export async function dispatchAction(input) {
14
+ // 1) 발행 자격(SEC-3) — ui.action 은 human/agent 만(브리지 위조 거부). 서명된 sender 분류로 강제.
15
+ if (!canPublish(EventType.UiAction, classifySender(input.sender))) {
16
+ return { status: "ignored", reason: "publish_forbidden", effects: [] };
17
+ }
18
+ // 2) 레지스트리 검증 — 스키마 불일치/알 수 없는 타입이면 무시.
19
+ const v = validateEvent(EventType.UiAction, input.actionContent);
20
+ if (!v.ok)
21
+ return { status: "ignored", reason: v.reason, effects: [] };
22
+ const action = v.data;
23
+ // 3) 앱이 아는 액션인가.
24
+ const def = input.app.actions.get(action.action_id);
25
+ if (!def)
26
+ return { status: "ignored", reason: "unknown_action", effects: [] };
27
+ // 4) 권한 재검증 — 호스트 게이트에 액션 정의의 scope/destructive 를 데이터로 주입(서버가 최종 결정).
28
+ const decision = input.gate({
29
+ senderId: input.sender,
30
+ actionId: action.action_id,
31
+ requiredScope: def.scope,
32
+ destructive: def.destructive,
33
+ powerLevels: input.powerLevels,
34
+ capability: input.capability,
35
+ });
36
+ if (!decision.allowed) {
37
+ return { status: "denied", actionId: action.action_id, reason: decision.reason, effects: [] };
38
+ }
39
+ // 5) 핸들러 실행 — 효과 수집(부작용은 호스트가 적용).
40
+ const handler = input.app.handlers.get(action.action_id);
41
+ const effects = [];
42
+ if (handler) {
43
+ const publish = makePublishHelpers(input.app, effects);
44
+ const ctx = {
45
+ sender: input.sender,
46
+ entity: input.entity ?? {},
47
+ cardId: action.card_id,
48
+ values: action.values ?? {},
49
+ cardEventId: input.cardEventId,
50
+ // backend 는 이 액션이 선언한 tools 화이트리스트로 감싼다(confused-deputy 방어, fail-closed).
51
+ backend: guardBackend(def, input.backend),
52
+ config: input.config,
53
+ editCard: (c) => effects.push({ kind: "edit", card: c }),
54
+ send: (c) => effects.push({ kind: "send", card: c }),
55
+ workflow: (status) => effects.push({ kind: "workflow", status }),
56
+ delegate: (d) => effects.push({ kind: "delegate", intent: d.intent, payload: d.payload }),
57
+ emit: publish.emit,
58
+ invoke: publish.invoke,
59
+ };
60
+ await handler(ctx);
61
+ }
62
+ return { status: "ran", actionId: action.action_id, effects };
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // 발행 헬퍼(emit/invoke) — ActionCtx/EventCtx 가 공유하는 효과 수집기
66
+ // ---------------------------------------------------------------------------
67
+ /**
68
+ * emit/invoke 효과 수집기를 만든다 — 두 컨텍스트(액션 핸들러·구독 핸들러)가 같은 규칙을 공유한다.
69
+ * emit 은 정의 시점이 아니라 *핸들러 실행 시점*에만 검증할 수 있으므로(타입/내용이 동적) 여기서 fail-fast throw
70
+ * 한다(outbound-strict — buildUiMessageContent 가 잘못된 블록을 거부하는 것과 같은 자세).
71
+ */
72
+ function makePublishHelpers(app, effects) {
73
+ return {
74
+ emit(type, content, opts) {
75
+ const form = opts?.form ?? "event";
76
+ // 소유 강제 — 이벤트 등록(register)·액션 id(assertActionNamespace)와 같은 규칙을 emit 에도 건다.
77
+ const prefix = `${NS}.${app.app}.`;
78
+ if (!type.startsWith(prefix)) {
79
+ throw new Error(`[app-sdk] 앱 '${app.app}': emit 타입 '${type}' 가 앱 네임스페이스 '${prefix}' 밖입니다 — ` +
80
+ `자기 사실만 발행할 수 있습니다(남의 이벤트에 반응하려면 on 구독을 쓰세요).`);
81
+ }
82
+ const declared = app.events[type];
83
+ let out = content;
84
+ if (declared) {
85
+ // 선언된 타입은 outbound-strict — 스키마 불일치를 발행 전에 잡는다(구독자가 쓰레기를 받지 않게).
86
+ const parsed = declared.schema.safeParse(content);
87
+ if (!parsed.success) {
88
+ throw new Error(`[app-sdk] 앱 '${app.app}': emit('${type}') content 가 선언 스키마와 불일치 — ${parsed.error.issues
89
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
90
+ .join("; ")}`);
91
+ }
92
+ out = parsed.data;
93
+ }
94
+ else if (form === "message") {
95
+ // message 형태는 카드를 그릴 view 가 필요하다 — 선언 없는 타입이면 그릴 방법이 없으니 즉시 알린다.
96
+ throw new Error(`[app-sdk] 앱 '${app.app}': emit('${type}', { form: "message" }) 는 events 에 선언된(view 있는) 타입만 가능합니다.`);
97
+ }
98
+ effects.push({
99
+ kind: "emit",
100
+ type,
101
+ content: out,
102
+ form,
103
+ ...(form === "state" ? { stateKey: opts?.stateKey ?? "" } : {}),
104
+ });
105
+ },
106
+ invoke(actionId, values) {
107
+ // 주소(<app>. 접두사) 없는 id 는 라우팅될 수 없다 — assertActionNamespace 가 보장하는 형태를 요구한다.
108
+ if (!actionId.includes(".")) {
109
+ throw new Error(`[app-sdk] invoke: action_id '${actionId}' 는 '<app>.<action>' 형태여야 합니다(접두사 = 라우팅 주소).`);
110
+ }
111
+ effects.push({ kind: "invoke", actionId, values: values ?? {} });
112
+ },
113
+ };
114
+ }
115
+ /**
116
+ * 액션이 선언한 tools 화이트리스트로 호스트 backend 를 감싼다(confused-deputy 방어).
117
+ * 핸들러가 ctx.backend.call(tool) 로 부를 수 있는 도구를 def.tools 로만 좁힌다 — 미선언/빈 배열이면
118
+ * 어떤 도구도 거부(fail-closed). 선언 밖 도구를 부르면 throw(1st-party 핸들러의 도구 드리프트/사고를 즉시 가시화).
119
+ * 강제는 SDK 가 기계적으로(선언 일치 검사), 도구 구현·권한은 호스트가 소유(의존성 역전, GateFn 과 같은 자세).
120
+ */
121
+ function guardBackend(def, raw) {
122
+ if (!raw)
123
+ return undefined;
124
+ const allowed = new Set(def.tools ?? []);
125
+ return {
126
+ call(tool, args) {
127
+ if (!allowed.has(tool)) {
128
+ throw new Error(`[app-sdk] 액션 '${def.id}' 가 미선언 도구 '${tool}' 를 호출했습니다 — ` +
129
+ `defineAction({ tools: [...] }) 에 선언해야 합니다(confused-deputy 방지, fail-closed).`);
130
+ }
131
+ return raw.call(tool, args);
132
+ },
133
+ };
134
+ }
135
+ /**
136
+ * 구독 이벤트 한 건을 라우팅한다 — 구독하지 않은 타입은 조용히 넘긴다(ignored, 거부 메시지 없음).
137
+ *
138
+ * 검증은 "로컬 지식 한정": 이 프로세스의 레지스트리가 아는 타입이면 스키마 불일치를 거르고(schema_mismatch),
139
+ * 모르는 타입(외부 앱의 사실)은 원본 그대로 핸들러에 넘긴다 — Matrix 는 임의 이벤트를 막지 못하므로 레지스트리는
140
+ * 강제가 아니라 합의 도구다(킥오프 §5). 발행 자격(canPublish) 게이트도 걸지 않는다 — 외부 봇은 human 으로
141
+ * 분류되어 정당한 연쇄가 막히기 때문에, 발신자 신뢰 판단은 sender 를 보고 호스트/핸들러가 한다.
142
+ *
143
+ * ⚠️ 루프 방지는 호스트 책임 — 자기(봇 계정)가 보낸 이벤트는 dispatchEvent 에 넣지 말 것(emit→on 무한 연쇄).
144
+ */
145
+ export async function dispatchEvent(input) {
146
+ const handler = input.app.on.get(input.type);
147
+ if (!handler)
148
+ return { status: "ignored", reason: "not_subscribed", effects: [] };
149
+ const v = validateEvent(input.type, input.content);
150
+ if (!v.ok && v.reason !== "unknown_type")
151
+ return { status: "ignored", reason: v.reason, effects: [] };
152
+ const effects = [];
153
+ const publish = makePublishHelpers(input.app, effects);
154
+ const ctx = {
155
+ type: input.type,
156
+ roomId: input.roomId,
157
+ sender: input.sender,
158
+ content: (v.ok ? v.data : input.content),
159
+ eventId: input.eventId,
160
+ send: (c) => effects.push({ kind: "send", card: c }),
161
+ emit: publish.emit,
162
+ invoke: publish.invoke,
163
+ };
164
+ await handler(ctx);
165
+ return { status: "ran", effects };
166
+ }
167
+ /**
168
+ * 핸들러가 선언한 효과를 Matrix 로 적용한다 — 호스트 무관(AS 에이전트·봇 계정·증명 스크립트가 같은 코드 사용).
169
+ * 배열 순서대로 적용한다. edit/send/emit/invoke 전송 실패는 throw(호스트가 회복 정책 소유),
170
+ * workflow 는 상태 이벤트 특성상 로그 후 계속(agent 레거시 동작 보존 — 카드 갱신을 막지 않는다).
171
+ */
172
+ export async function applyEffects(input) {
173
+ // SDK 는 런타임 환경을 가정하지 않는다(lib 에 console 없음) — 기본 로그는 globalThis.console 이 있을 때만.
174
+ const g = globalThis;
175
+ const warn = input.warn ?? ((m) => g.console?.warn(m));
176
+ const error = input.error ?? ((m) => g.console?.error(m));
177
+ for (const e of input.effects) {
178
+ switch (e.kind) {
179
+ case "edit": {
180
+ // 편집 대상 카드가 있으면 m.replace, 없으면(참조 유실) 새 카드로 graceful.
181
+ const msg = input.cardEventId ? buildUiMessageEdit(input.cardEventId, e.card) : buildUiMessageContent(e.card);
182
+ await input.client.sendEvent(input.roomId, "m.room.message", msg);
183
+ break;
184
+ }
185
+ case "send":
186
+ await input.client.sendEvent(input.roomId, "m.room.message", buildUiMessageContent(e.card));
187
+ break;
188
+ case "workflow": {
189
+ if (!input.entity?.id) {
190
+ warn(`[app-sdk] workflow 효과 생략 — 엔티티 id 미해석 (status=${e.status})`);
191
+ break;
192
+ }
193
+ const content = {
194
+ v: 1,
195
+ status: e.status,
196
+ entity: { type: input.entity.type ?? "entity", id: input.entity.id },
197
+ updated_by: input.sender,
198
+ updated_at: Date.now(),
199
+ };
200
+ try {
201
+ await input.client.sendStateEvent(input.roomId, EventType.WorkflowState, input.entity.id, content);
202
+ }
203
+ catch (err) {
204
+ error(`[app-sdk] workflow.state 게시 실패 (${input.entity.id}, ${e.status}): ${err instanceof Error ? err.message : String(err)}`);
205
+ }
206
+ break;
207
+ }
208
+ case "delegate":
209
+ if (input.onDelegate)
210
+ await input.onDelegate(e);
211
+ else
212
+ warn(`[app-sdk] delegate 효과 무시 — 호스트가 onDelegate 를 주입하지 않았습니다 (intent=${e.intent})`);
213
+ break;
214
+ case "emit": {
215
+ if (e.form === "message") {
216
+ // 선언 view 로 카드를 그려 m.room.message 로 — emit 시점 검증을 통과한 content 라 보통 성공한다.
217
+ const msg = input.app.messageForEvent(e.type, e.content);
218
+ if (!msg) {
219
+ warn(`[app-sdk] emit(message) 생략 — '${e.type}' 의 view/스키마 해석 실패`);
220
+ break;
221
+ }
222
+ await input.client.sendEvent(input.roomId, "m.room.message", msg);
223
+ }
224
+ else if (e.form === "state") {
225
+ await input.client.sendStateEvent(input.roomId, e.type, e.stateKey ?? "", e.content);
226
+ }
227
+ else {
228
+ await input.client.sendEvent(input.roomId, e.type, e.content);
229
+ }
230
+ break;
231
+ }
232
+ case "invoke": {
233
+ // UI 없는 호출 — 카드가 없으므로 card_id 는 발신 앱 표기(수신 라우팅은 action_id 접두사가 한다).
234
+ const content = buildUiActionContent({
235
+ card_id: `invoke:${input.app.app}`,
236
+ action_id: e.actionId,
237
+ action_type: "submit",
238
+ values: e.values,
239
+ });
240
+ await input.client.sendEvent(input.roomId, EventType.UiAction, content);
241
+ break;
242
+ }
243
+ }
244
+ }
245
+ }
246
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import type { App } from "./app.js";
2
+ import { type ActionBackend, type ApplyEffectsInput, type GateFn } from "./runtime.js";
3
+ import { type MatrixHostClient, type PublishSurfaceResult } from "./host.js";
4
+ export interface FetchResponseLike {
5
+ ok: boolean;
6
+ status: number;
7
+ text(): Promise<string>;
8
+ }
9
+ export type FetchLike = (url: string, init?: {
10
+ method?: string;
11
+ headers?: Record<string, string>;
12
+ body?: string;
13
+ }) => Promise<FetchResponseLike>;
14
+ /**
15
+ * Matrix API 오류(구조화) — 호출자가 상태/errcode 로 회복 정책을 분기할 수 있게 한다
16
+ * (401/403=재시도 무의미 fail-fast, 429=retry_after_ms 준수, 그 외=백오프 재시도).
17
+ */
18
+ export declare class MatrixApiError extends Error {
19
+ readonly status: number;
20
+ readonly errcode?: string;
21
+ readonly retryAfterMs?: number;
22
+ constructor(message: string, status: number, errcode?: string, retryAfterMs?: number);
23
+ }
24
+ export interface BotClientOptions {
25
+ /** 홈서버 베이스 URL (예: "https://hs.example.com"). */
26
+ homeserver: string;
27
+ /** 봇 계정 액세스 토큰 — 이것 하나면 된다(등록 파일 불필요). */
28
+ accessToken: string;
29
+ /** fetch 주입(기본 globalThis.fetch) — 증명/테스트가 가짜 홈서버를 꽂는 자리. */
30
+ fetch?: FetchLike;
31
+ }
32
+ /** /sync 응답의 최소 형태(우리가 읽는 부분만). */
33
+ export interface SyncResponse {
34
+ next_batch: string;
35
+ rooms?: {
36
+ invite?: Record<string, {
37
+ invite_state?: {
38
+ events?: SyncEvent[];
39
+ };
40
+ } | undefined>;
41
+ join?: Record<string, {
42
+ timeline?: {
43
+ events?: SyncEvent[];
44
+ limited?: boolean;
45
+ prev_batch?: string;
46
+ };
47
+ }>;
48
+ };
49
+ }
50
+ export interface SyncEvent {
51
+ type?: string;
52
+ sender?: string;
53
+ event_id?: string;
54
+ content?: Record<string, unknown>;
55
+ state_key?: string;
56
+ /** 홈서버가 찍은 발생 시각(ms) — join 백필(과거 이벤트) 워터마크 판정에 쓴다. */
57
+ origin_server_ts?: number;
58
+ }
59
+ export interface BotClient extends MatrixHostClient {
60
+ getRoomStateEvent(roomId: string, eventType: string, stateKey: string): Promise<unknown>;
61
+ whoami(): Promise<string>;
62
+ sync(since: string | undefined, timeoutMs: number, filter?: string): Promise<SyncResponse>;
63
+ }
64
+ /** 토큰 하나로 도는 최소 클라이언트를 만든다. 전송 규약 주의: 메시지는 PUT .../send/{type}/{txnId} (§7 — POST 는 404). */
65
+ export declare function createBotClient(opts: BotClientOptions): BotClient;
66
+ export interface PublishOptions extends BotClientOptions {
67
+ roomId: string;
68
+ /** 서피스 state_key — 기본 app.app(한 방에 여러 앱이 공존해도 서로 안 덮는다). 레거시 단일 뷰는 "". */
69
+ stateKey?: string;
70
+ }
71
+ /** 앱 서피스를 방에 1회 발행하고 끝낸다. 서피스는 상태 이벤트라 한 번 올리면 영속한다(§1.3). */
72
+ export declare function publish(app: App, opts: PublishOptions): Promise<PublishSurfaceResult>;
73
+ export interface ServeOptions extends BotClientOptions {
74
+ /** 서피스 state_key — 기본 app.app(멀티앱 방 공존). 레거시 단일-앱 방 뷰로 붙이려면 "". */
75
+ surfaceStateKey?: string;
76
+ /**
77
+ * 액션 권한 게이트(선택) — 미주입이면 **기본 게이트**: 비파괴는 허용, 파괴적(destructive) 액션은 발신자의
78
+ * 방 power level ≥ 50 을 요구한다(§6-3 결정 — 서버 정책 DESTRUCTIVE_PL_THRESHOLD 와 같은 임계.
79
+ * Matrix PL 이라는 방 네이티브 권위 재사용). 주입하면 완전히 대체된다(정책은 앱 소유).
80
+ */
81
+ gate?: GateFn;
82
+ /**
83
+ * 초대 수락 판정(선택) — 미주입이면 **모든 초대 수락**(§2.4 "초대하면 활성화"). 좁히려면 콜백으로
84
+ * roomId/초대자(inviter)를 보고 결정한다. false 를 돌려주면 join 하지 않고 조용히 넘긴다(로그만).
85
+ */
86
+ acceptInvite?: (invite: {
87
+ roomId: string;
88
+ inviter?: string;
89
+ }) => boolean | Promise<boolean>;
90
+ /** 핸들러 read-then-decide 백엔드(선택) — dispatchAction 의 ctx.backend 로 전달. */
91
+ backend?: ActionBackend;
92
+ /** 앱 정책 설정(선택) — ctx.config 로 전달. */
93
+ config?: Record<string, unknown>;
94
+ /** delegate 효과 콜백(선택) — 미주입이면 경고 후 무시(applyEffects 규칙). */
95
+ onDelegate?: ApplyEffectsInput["onDelegate"];
96
+ /** sync long-poll 타임아웃 ms(기본 30000). */
97
+ syncTimeoutMs?: number;
98
+ /** 종료 신호(AbortSignal 호환) — aborted 되면 현재 배치 처리 후 루프를 빠져나온다. */
99
+ signal?: {
100
+ aborted: boolean;
101
+ };
102
+ /** 진행 로그 훅(기본 globalThis.console.log). */
103
+ log?: (msg: string) => void;
104
+ }
105
+ /** 봇 모드 파괴적 액션 PL 임계 — 서버 정책(DESTRUCTIVE_PL_THRESHOLD)과 같은 값. */
106
+ export declare const BOT_DESTRUCTIVE_PL_THRESHOLD = 50;
107
+ /**
108
+ * 기본 봇 게이트(§6-3 결정) — 비파괴 액션은 허용, 파괴적(destructive) 액션은 발신자의 방 PL ≥ 50 요구.
109
+ * 방장이 PL 로 권한을 나눠주는 Matrix 네이티브 권위를 재사용한다(서피스 발행 권한과 같은 원리).
110
+ * opts.gate 를 주입하면 완전히 대체된다.
111
+ */
112
+ export declare const defaultBotGate: GateFn;
113
+ /**
114
+ * 앱을 봇 계정으로 상시 실행한다. 초대를 자동 수락해 서피스를 1회 발행하고(§2.4), sync 타임라인에서
115
+ * 자기 소유 액션·구독 이벤트만 처리한다(아니면 무발행 무시). signal.aborted 로 정상 종료할 수 있다.
116
+ * 토큰이 죽으면(401/403) 재시도가 무의미하므로 reject 로 즉시 알린다 — 그 외 sync 오류는 백오프 후 재시도.
117
+ */
118
+ export declare function serve(app: App, opts: ServeOptions): Promise<void>;
119
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}