@obov/mext 0.3.0 → 0.4.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/dist/build.js +67 -4
- package/dist/build.js.map +1 -1
- package/dist/cli/ask-url.d.ts +16 -0
- package/dist/cli/ask-url.js +27 -0
- package/dist/cli/ask-url.js.map +1 -0
- package/dist/cli/build.js +7 -1
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev-server.d.ts +7 -1
- package/dist/cli/dev-server.js +15 -2
- package/dist/cli/dev-server.js.map +1 -1
- package/dist/cli/dev.js +27 -7
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/login.d.ts +5 -0
- package/dist/cli/login.js +11 -7
- package/dist/cli/login.js.map +1 -1
- package/dist/cli/logout.js +4 -6
- package/dist/cli/logout.js.map +1 -1
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js +1 -2
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/run.js +21 -5
- package/dist/cli/run.js.map +1 -1
- package/dist/client/cache.d.ts +14 -4
- package/dist/client/cache.js +10 -3
- package/dist/client/cache.js.map +1 -1
- package/dist/client/main.js +11 -10
- package/dist/client/main.js.map +1 -1
- package/dist/client/manifest.d.ts +13 -1
- package/dist/client/manifest.js +43 -0
- package/dist/client/manifest.js.map +1 -1
- package/dist/client/runner.d.ts +3 -1
- package/dist/client/runner.js +80 -19
- package/dist/client/runner.js.map +1 -1
- package/dist/client/task.d.ts +47 -0
- package/dist/client/task.js +126 -0
- package/dist/client/task.js.map +1 -0
- package/dist/command.d.ts +30 -3
- package/dist/command.js +16 -0
- package/dist/command.js.map +1 -1
- package/dist/config.d.ts +40 -6
- package/dist/config.js +57 -8
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.d.ts +14 -2
- package/dist/instrumentation.js +12 -4
- package/dist/instrumentation.js.map +1 -1
- package/dist/invoke.d.ts +38 -0
- package/dist/invoke.js +72 -3
- package/dist/invoke.js.map +1 -1
- package/dist/local-bin.js +17 -9
- package/dist/local-bin.js.map +1 -1
- package/dist/manifest.d.ts +48 -3
- package/dist/manifest.js +14 -3
- package/dist/manifest.js.map +1 -1
- package/dist/mcp/constants.d.ts +4 -0
- package/dist/mcp/constants.js +4 -0
- package/dist/mcp/constants.js.map +1 -1
- package/dist/mcp/dispatch.d.ts +21 -7
- package/dist/mcp/dispatch.js +42 -7
- package/dist/mcp/dispatch.js.map +1 -1
- package/dist/mcp/http.d.ts +23 -5
- package/dist/mcp/http.js +64 -9
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/index.d.ts +14 -6
- package/dist/mcp/index.js +7 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/mrtr.d.ts +40 -8
- package/dist/mcp/mrtr.js +32 -11
- package/dist/mcp/mrtr.js.map +1 -1
- package/dist/mcp/prompts.d.ts +25 -0
- package/dist/mcp/prompts.js +164 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +48 -0
- package/dist/mcp/resources.js +142 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +30 -5
- package/dist/mcp/server.js +143 -16
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts +5 -0
- package/dist/mcp/stdio.js +16 -3
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp/subscriptions.d.ts +61 -0
- package/dist/mcp/subscriptions.js +188 -0
- package/dist/mcp/subscriptions.js.map +1 -0
- package/dist/mcp/tasks.d.ts +127 -0
- package/dist/mcp/tasks.js +448 -0
- package/dist/mcp/tasks.js.map +1 -0
- package/dist/module-files.d.ts +24 -0
- package/dist/module-files.js +97 -0
- package/dist/module-files.js.map +1 -0
- package/dist/prompt.d.ts +133 -0
- package/dist/prompt.js +222 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js.map +1 -1
- package/dist/remote/client.d.ts +17 -1
- package/dist/remote/client.js +18 -2
- package/dist/remote/client.js.map +1 -1
- package/dist/resource.d.ts +169 -0
- package/dist/resource.js +301 -0
- package/dist/resource.js.map +1 -0
- package/dist/route.d.ts +71 -0
- package/dist/route.js +280 -0
- package/dist/route.js.map +1 -0
- package/dist/start.d.ts +14 -3
- package/dist/start.js +76 -11
- package/dist/start.js.map +1 -1
- package/dist/state.d.ts +38 -0
- package/dist/state.js +98 -0
- package/dist/state.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { STATE_KEY_PREFIX } from "../state.js";
|
|
5
|
+
import { errorResult, valueResult } from "./dispatch.js";
|
|
6
|
+
import { ASK_KEY_PREFIX, askAnswer, elicitation, elicitResult, modeUnavailable, } from "./mrtr.js";
|
|
7
|
+
/**
|
|
8
|
+
* MCP tasks 확장 (io.modelcontextprotocol/tasks, §12.6). 서버 측: task 생성 (invoke detach) · tasks/get · update · cancel.
|
|
9
|
+
*
|
|
10
|
+
* D5: SDK 2.4.0 은 tasks 런타임이 없다 (task 메서드 = 2025-11-25 어휘, typed API 미제공). 단 3-arg setRequestHandler
|
|
11
|
+
* (custom params schema) 로 tasks/get · update · cancel 을 등록할 수 있고 (era gate 통과), Mcp-Name = params.taskId 헤더 검증은
|
|
12
|
+
* SDK 가 한다. tools/call 결과 resultType "task" 는 SDK 가 그대로 통과시킨다. 그래서 mext 가 이 파일에서 직접 구현한다.
|
|
13
|
+
*
|
|
14
|
+
* D2: store 키 (값 = JSON, 접두 "mext:", 키마다 writer 하나)
|
|
15
|
+
* mext:task:<taskId> TaskRecord. writer = 실행 프로세스 (생성 · 상태 · heartbeat · 결과)
|
|
16
|
+
* mext:task:<taskId>:input:<ask-N> 답 (ElicitResult). writer = tasks/update 를 받은 프로세스. 이미 있으면 무시
|
|
17
|
+
* mext:task:<taskId>:cancel 취소 기록 ({}). writer = tasks/cancel 을 받은 프로세스
|
|
18
|
+
* bus 채널 mext:task:<taskId> = 답 · 취소 도착 알림 ({"type":"input"|"cancel"}). 내용은 store 가 원본, 메시지는 깨우기만.
|
|
19
|
+
* bus 없음 = 실행 프로세스가 storePollMs (1초) 마다 store 폴링. 같은 프로세스의 task = 직접 깨움.
|
|
20
|
+
* store ttl: 실행 중 = ttlMs + lostAfterMs (heartbeat 마다 연장, 유실 후 failed 를 ttlMs 동안 조회), 종료 = ttlMs.
|
|
21
|
+
*
|
|
22
|
+
* R4: ctx.ask 의미 차이. MRTR (동기) = 라운드마다 핸들러 재실행 (ask 이전 코드 반복), task = 재실행 없이 대기
|
|
23
|
+
* (ask 이전 코드 1회). 키 (ask-0 · ask-1 …) · elicitation 형식 · 답 해석 (askAnswer) · 미선언 mode = INPUT_UNAVAILABLE 은 같다.
|
|
24
|
+
* 두 경로 모두에서 맞게 동작하려면 MRTR 규칙 (부작용은 ask 뒤) 을 따른다.
|
|
25
|
+
*/
|
|
26
|
+
export const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
27
|
+
export const TASK_TIMING = Object.freeze({
|
|
28
|
+
pollIntervalMs: 2_000,
|
|
29
|
+
heartbeatMs: 10_000,
|
|
30
|
+
lostAfterMs: 30_000,
|
|
31
|
+
storePollMs: 1_000,
|
|
32
|
+
shutdownMs: 5_000,
|
|
33
|
+
});
|
|
34
|
+
/** tasks/get failed 의 error.data.code (실행 프로세스 유실) */
|
|
35
|
+
export const TASK_LOST = "TASK_LOST";
|
|
36
|
+
/** registry 에 task 명령어가 있는지 (선언 조건) */
|
|
37
|
+
export function hasTaskCommands(registry) {
|
|
38
|
+
for (const entry of registry.commands.values())
|
|
39
|
+
if (entry.command.task)
|
|
40
|
+
return true;
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
/** 요청 _meta 의 clientCapabilities 에 tasks 확장 선언 */
|
|
44
|
+
export function declaresTasks(clientCapabilities) {
|
|
45
|
+
const extensions = clientCapabilities?.extensions;
|
|
46
|
+
return isPlainObject(extensions) && extensions[TASKS_EXTENSION] !== undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* HTTP: task 가 될 수 있는 tools/call 인지 (응답 = application/json, §12.6). 확장 선언 + 대상 명령어가 task 명령어.
|
|
50
|
+
* 대상 = dispatcher 의 arguments.command (별칭 포함), 그 외 = 도구 이름 (command 노출)
|
|
51
|
+
*/
|
|
52
|
+
export function requestsTask(registry, body) {
|
|
53
|
+
if (!isPlainObject(body) || body.method !== "tools/call" || !isPlainObject(body.params))
|
|
54
|
+
return false;
|
|
55
|
+
const params = body.params;
|
|
56
|
+
const meta = isPlainObject(params._meta) ? params._meta : undefined;
|
|
57
|
+
if (!declaresTasks(meta?.["io.modelcontextprotocol/clientCapabilities"]))
|
|
58
|
+
return false;
|
|
59
|
+
const args = isPlainObject(params.arguments) ? params.arguments : undefined;
|
|
60
|
+
const target = typeof args?.command === "string" ? args.command : params.name;
|
|
61
|
+
if (typeof target !== "string")
|
|
62
|
+
return false;
|
|
63
|
+
const id = registry.commands.has(target) ? target : registry.aliases.get(target);
|
|
64
|
+
return id !== undefined && registry.commands.get(id)?.command.task !== undefined;
|
|
65
|
+
}
|
|
66
|
+
const recordKey = (taskId) => `${STATE_KEY_PREFIX}task:${taskId}`;
|
|
67
|
+
const inputKey = (taskId, key) => `${recordKey(taskId)}:input:${key}`;
|
|
68
|
+
const cancelKey = (taskId) => `${recordKey(taskId)}:cancel`;
|
|
69
|
+
const taskChannel = (taskId) => `${STATE_KEY_PREFIX}task:${taskId}`;
|
|
70
|
+
const TERMINAL = new Set(["completed", "failed", "cancelled"]);
|
|
71
|
+
export function createTaskRuntime(options) {
|
|
72
|
+
const { store, bus } = options.state;
|
|
73
|
+
const timing = { ...TASK_TIMING, ...options.timing };
|
|
74
|
+
const meta = options.meta ?? {};
|
|
75
|
+
const report = options.onerror ?? defaultOnError;
|
|
76
|
+
const running = new Map();
|
|
77
|
+
let closed = false;
|
|
78
|
+
/** store · bus 호출. 예외 = 보고 + -32603 */
|
|
79
|
+
const storeCall = async (fn) => {
|
|
80
|
+
try {
|
|
81
|
+
return await fn();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
safeReport(report, error);
|
|
85
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, "State store error");
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const readRecord = async (taskId, principal) => {
|
|
89
|
+
const raw = await storeCall(() => store.get(recordKey(taskId)));
|
|
90
|
+
const record = raw === undefined ? undefined : parseRecord(raw);
|
|
91
|
+
if (!record || record.taskId !== taskId || record.principal !== bindingOf(principal))
|
|
92
|
+
throw unknownTask(taskId);
|
|
93
|
+
return record;
|
|
94
|
+
};
|
|
95
|
+
const lost = (record) => !TERMINAL.has(record.status) && Date.now() - record.heartbeatAt > timing.lostAfterMs;
|
|
96
|
+
/** 답 · 취소 도착 알림: 같은 프로세스 = 직접, 그 외 = bus (없으면 실행 프로세스가 폴링) */
|
|
97
|
+
const notify = async (taskId, type) => {
|
|
98
|
+
const local = running.get(taskId);
|
|
99
|
+
if (local)
|
|
100
|
+
return void (await local.wake());
|
|
101
|
+
if (bus)
|
|
102
|
+
await storeCall(() => bus.publish(taskChannel(taskId), JSON.stringify({ type })));
|
|
103
|
+
};
|
|
104
|
+
const start = async (entry, request) => {
|
|
105
|
+
const taskId = randomBytes(16).toString("base64url");
|
|
106
|
+
const created = new Date().toISOString();
|
|
107
|
+
const record = {
|
|
108
|
+
v: 1,
|
|
109
|
+
taskId,
|
|
110
|
+
principal: bindingOf(request.principal),
|
|
111
|
+
command: entry.id,
|
|
112
|
+
status: "working",
|
|
113
|
+
createdAt: created,
|
|
114
|
+
lastUpdatedAt: created,
|
|
115
|
+
ttlMs: entry.command.task.ttlMs,
|
|
116
|
+
heartbeatAt: Date.now(),
|
|
117
|
+
};
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
const waiters = new Map();
|
|
120
|
+
let finalized = false;
|
|
121
|
+
let resolveDone;
|
|
122
|
+
const done = new Promise((resolve) => (resolveDone = resolve));
|
|
123
|
+
const touch = () => void (record.lastUpdatedAt = new Date().toISOString());
|
|
124
|
+
// 순서 보장 · 합치기: 쓰는 중 변경 = 끝난 뒤 최신 상태로 한 번 더
|
|
125
|
+
let writing;
|
|
126
|
+
let dirty = false;
|
|
127
|
+
const persist = () => {
|
|
128
|
+
dirty = true;
|
|
129
|
+
writing ??= (async () => {
|
|
130
|
+
try {
|
|
131
|
+
while (dirty) {
|
|
132
|
+
dirty = false;
|
|
133
|
+
const ttlMs = TERMINAL.has(record.status) ? record.ttlMs : record.ttlMs + timing.lostAfterMs;
|
|
134
|
+
await store.set(recordKey(taskId), JSON.stringify(record), { ttlMs });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
writing = undefined;
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
return writing;
|
|
142
|
+
};
|
|
143
|
+
const persistLater = () => void persist().catch((error) => safeReport(report, error));
|
|
144
|
+
const wake = async () => {
|
|
145
|
+
try {
|
|
146
|
+
if (!controller.signal.aborted && (await store.get(cancelKey(taskId))) !== undefined) {
|
|
147
|
+
controller.abort(new DOMException("Task cancelled", "AbortError"));
|
|
148
|
+
}
|
|
149
|
+
for (const [key, resolve] of [...waiters]) {
|
|
150
|
+
const raw = await store.get(inputKey(taskId, key));
|
|
151
|
+
const answer = raw === undefined ? undefined : elicitResult(parseJson(raw));
|
|
152
|
+
if (answer)
|
|
153
|
+
resolve(answer);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
safeReport(report, error);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
let heartbeat;
|
|
161
|
+
let poll;
|
|
162
|
+
let unsubscribe;
|
|
163
|
+
const stopTimers = () => {
|
|
164
|
+
if (heartbeat !== undefined)
|
|
165
|
+
clearInterval(heartbeat);
|
|
166
|
+
if (poll !== undefined)
|
|
167
|
+
clearInterval(poll);
|
|
168
|
+
};
|
|
169
|
+
const finish = async () => {
|
|
170
|
+
if (finalized)
|
|
171
|
+
return;
|
|
172
|
+
finalized = true;
|
|
173
|
+
stopTimers();
|
|
174
|
+
delete record.inputRequests;
|
|
175
|
+
touch();
|
|
176
|
+
waiters.clear();
|
|
177
|
+
try {
|
|
178
|
+
await persist();
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
safeReport(report, error);
|
|
182
|
+
}
|
|
183
|
+
running.delete(taskId);
|
|
184
|
+
try {
|
|
185
|
+
await unsubscribe?.();
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
safeReport(report, error);
|
|
189
|
+
}
|
|
190
|
+
resolveDone();
|
|
191
|
+
};
|
|
192
|
+
let index = 0;
|
|
193
|
+
const ask = async (askRequest) => {
|
|
194
|
+
const key = `${ASK_KEY_PREFIX}${index++}`;
|
|
195
|
+
if (!(askRequest.kind === "url" ? request.modes.url : request.modes.form))
|
|
196
|
+
throw modeUnavailable(askRequest);
|
|
197
|
+
controller.signal.throwIfAborted();
|
|
198
|
+
let onAbort;
|
|
199
|
+
const answered = new Promise((resolve, reject) => {
|
|
200
|
+
onAbort = () => {
|
|
201
|
+
waiters.delete(key);
|
|
202
|
+
reject(controller.signal.reason);
|
|
203
|
+
};
|
|
204
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
205
|
+
waiters.set(key, (answer) => {
|
|
206
|
+
waiters.delete(key);
|
|
207
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
208
|
+
resolve(answer);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
answered.catch(() => { });
|
|
212
|
+
record.inputRequests = { ...record.inputRequests, [key]: elicitation(askRequest) };
|
|
213
|
+
record.status = "input_required";
|
|
214
|
+
touch();
|
|
215
|
+
try {
|
|
216
|
+
await persist();
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
waiters.delete(key);
|
|
220
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
const response = await answered;
|
|
224
|
+
if (record.inputRequests) {
|
|
225
|
+
delete record.inputRequests[key];
|
|
226
|
+
if (Object.keys(record.inputRequests).length === 0)
|
|
227
|
+
delete record.inputRequests;
|
|
228
|
+
}
|
|
229
|
+
if (!record.inputRequests && !finalized)
|
|
230
|
+
record.status = "working";
|
|
231
|
+
touch();
|
|
232
|
+
if (!finalized)
|
|
233
|
+
persistLater();
|
|
234
|
+
return askAnswer(askRequest, response);
|
|
235
|
+
};
|
|
236
|
+
const progress = (update) => {
|
|
237
|
+
if (finalized)
|
|
238
|
+
return;
|
|
239
|
+
record.statusMessage =
|
|
240
|
+
update.message ?? (update.total !== undefined ? `${update.progress}/${update.total}` : String(update.progress));
|
|
241
|
+
touch();
|
|
242
|
+
persistLater();
|
|
243
|
+
};
|
|
244
|
+
const task = {
|
|
245
|
+
wake,
|
|
246
|
+
abort: (reason) => controller.abort(reason),
|
|
247
|
+
done,
|
|
248
|
+
async forceCancel() {
|
|
249
|
+
if (finalized)
|
|
250
|
+
return;
|
|
251
|
+
record.status = "cancelled";
|
|
252
|
+
await finish();
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
running.set(taskId, task);
|
|
256
|
+
try {
|
|
257
|
+
if (bus)
|
|
258
|
+
unsubscribe = await bus.subscribe(taskChannel(taskId), () => void wake());
|
|
259
|
+
// store 기록 후 응답 (tasks/get 이 바로 성공해야 한다)
|
|
260
|
+
await persist();
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
running.delete(taskId);
|
|
264
|
+
await unsubscribe?.().catch(() => { });
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
heartbeat = setInterval(() => {
|
|
268
|
+
record.heartbeatAt = Date.now();
|
|
269
|
+
persistLater();
|
|
270
|
+
}, timing.heartbeatMs);
|
|
271
|
+
heartbeat.unref?.();
|
|
272
|
+
if (!bus) {
|
|
273
|
+
poll = setInterval(() => void wake(), timing.storePollMs);
|
|
274
|
+
poll.unref?.();
|
|
275
|
+
}
|
|
276
|
+
request.waitUntil?.(done);
|
|
277
|
+
const handle = {
|
|
278
|
+
resultType: "task",
|
|
279
|
+
taskId,
|
|
280
|
+
status: "working",
|
|
281
|
+
createdAt: record.createdAt,
|
|
282
|
+
lastUpdatedAt: record.lastUpdatedAt,
|
|
283
|
+
ttlMs: record.ttlMs,
|
|
284
|
+
pollIntervalMs: timing.pollIntervalMs,
|
|
285
|
+
};
|
|
286
|
+
return {
|
|
287
|
+
handle,
|
|
288
|
+
signal: controller.signal,
|
|
289
|
+
ask,
|
|
290
|
+
progress,
|
|
291
|
+
async settle(outcome) {
|
|
292
|
+
if (finalized)
|
|
293
|
+
return;
|
|
294
|
+
if ("error" in outcome && controller.signal.aborted) {
|
|
295
|
+
// 취소 후 예외 = cancelled. 핸들러가 끝까지 가면 (값 반환) 그 결과
|
|
296
|
+
record.status = "cancelled";
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
record.status = "completed";
|
|
300
|
+
const result = "error" in outcome ? errorResult(outcome.error) : valueResult(outcome.value);
|
|
301
|
+
record.result = { ...result, resultType: "complete", _meta: { ...meta } };
|
|
302
|
+
}
|
|
303
|
+
await finish();
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
return {
|
|
308
|
+
timing,
|
|
309
|
+
request(requestOptions) {
|
|
310
|
+
const out = {
|
|
311
|
+
failure: undefined,
|
|
312
|
+
async detach(entry) {
|
|
313
|
+
if (!entry.command.task || closed)
|
|
314
|
+
return undefined;
|
|
315
|
+
try {
|
|
316
|
+
return await start(entry, requestOptions);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
safeReport(report, error);
|
|
320
|
+
out.failure = error;
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
return out;
|
|
326
|
+
},
|
|
327
|
+
async get(taskId, principal) {
|
|
328
|
+
const record = await readRecord(taskId, principal);
|
|
329
|
+
const view = {
|
|
330
|
+
taskId: record.taskId,
|
|
331
|
+
status: record.status,
|
|
332
|
+
...(record.statusMessage !== undefined ? { statusMessage: record.statusMessage } : {}),
|
|
333
|
+
createdAt: record.createdAt,
|
|
334
|
+
lastUpdatedAt: record.lastUpdatedAt,
|
|
335
|
+
ttlMs: record.ttlMs,
|
|
336
|
+
pollIntervalMs: timing.pollIntervalMs,
|
|
337
|
+
};
|
|
338
|
+
if (lost(record)) {
|
|
339
|
+
view.status = "failed";
|
|
340
|
+
view.error = {
|
|
341
|
+
code: ProtocolErrorCode.InternalError,
|
|
342
|
+
message: `Task lost: the executing process stopped (no heartbeat for ${timing.lostAfterMs}ms).`,
|
|
343
|
+
data: { code: TASK_LOST },
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
else if (record.status === "input_required" && record.inputRequests)
|
|
347
|
+
view.inputRequests = record.inputRequests;
|
|
348
|
+
else if (record.status === "completed" && record.result)
|
|
349
|
+
view.result = record.result;
|
|
350
|
+
return view;
|
|
351
|
+
},
|
|
352
|
+
async update(taskId, principal, inputResponses) {
|
|
353
|
+
const record = await readRecord(taskId, principal);
|
|
354
|
+
if (TERMINAL.has(record.status) || lost(record))
|
|
355
|
+
return;
|
|
356
|
+
let wrote = false;
|
|
357
|
+
for (const [key, value] of Object.entries(inputResponses)) {
|
|
358
|
+
// 모르는 키 · 형식이 아닌 답 · 이미 답한 키 = 무시
|
|
359
|
+
if (record.inputRequests?.[key] === undefined || !elicitResult(value))
|
|
360
|
+
continue;
|
|
361
|
+
const stored = await storeCall(() => store.get(inputKey(taskId, key)));
|
|
362
|
+
if (stored !== undefined)
|
|
363
|
+
continue;
|
|
364
|
+
await storeCall(() => store.set(inputKey(taskId, key), JSON.stringify(value), { ttlMs: record.ttlMs + timing.lostAfterMs }));
|
|
365
|
+
wrote = true;
|
|
366
|
+
}
|
|
367
|
+
if (wrote)
|
|
368
|
+
await notify(taskId, "input");
|
|
369
|
+
},
|
|
370
|
+
async cancel(taskId, principal) {
|
|
371
|
+
const record = await readRecord(taskId, principal);
|
|
372
|
+
if (TERMINAL.has(record.status) || lost(record))
|
|
373
|
+
return;
|
|
374
|
+
await storeCall(() => store.set(cancelKey(taskId), "{}", { ttlMs: record.ttlMs + timing.lostAfterMs }));
|
|
375
|
+
await notify(taskId, "cancel");
|
|
376
|
+
},
|
|
377
|
+
async close() {
|
|
378
|
+
closed = true;
|
|
379
|
+
const tasks = [...running.values()];
|
|
380
|
+
if (tasks.length === 0)
|
|
381
|
+
return;
|
|
382
|
+
for (const task of tasks)
|
|
383
|
+
task.abort(new DOMException("Server shutting down", "AbortError"));
|
|
384
|
+
let timer;
|
|
385
|
+
await Promise.race([
|
|
386
|
+
Promise.all(tasks.map((task) => task.done)),
|
|
387
|
+
new Promise((resolve) => (timer = setTimeout(resolve, timing.shutdownMs))),
|
|
388
|
+
]);
|
|
389
|
+
clearTimeout(timer);
|
|
390
|
+
await Promise.all(tasks.map((task) => task.forceCancel()));
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* McpServer 에 tasks/get · tasks/update · tasks/cancel 등록 (D5: 3-arg setRequestHandler, custom params schema).
|
|
396
|
+
* declare = server/discover capabilities.extensions 에 확장 선언 (생성 가능할 때만). transport 연결 전에 호출
|
|
397
|
+
*/
|
|
398
|
+
export function installTaskHandlers(server, runtime, principal, declare) {
|
|
399
|
+
const inner = server.server;
|
|
400
|
+
if (declare)
|
|
401
|
+
inner.registerCapabilities({ extensions: { [TASKS_EXTENSION]: {} } });
|
|
402
|
+
const byId = z.object({ taskId: z.string() });
|
|
403
|
+
inner.setRequestHandler("tasks/get", { params: byId }, async ({ taskId }) => ({ ...(await runtime.get(taskId, principal)) }));
|
|
404
|
+
// inputResponses 는 SDK 가 params 에서 분리 (MRTR 예약 키) → ctx.mcpReq.inputResponses (bare 결과만)
|
|
405
|
+
inner.setRequestHandler("tasks/update", { params: byId }, async ({ taskId }, ctx) => {
|
|
406
|
+
await runtime.update(taskId, principal, ctx.mcpReq.inputResponses ?? {});
|
|
407
|
+
return {};
|
|
408
|
+
});
|
|
409
|
+
inner.setRequestHandler("tasks/cancel", { params: byId }, async ({ taskId }) => {
|
|
410
|
+
await runtime.cancel(taskId, principal);
|
|
411
|
+
return {};
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function unknownTask(taskId) {
|
|
415
|
+
return new ProtocolError(ProtocolErrorCode.InvalidParams, `Unknown task: ${taskId}`);
|
|
416
|
+
}
|
|
417
|
+
function bindingOf(principal) {
|
|
418
|
+
return `${principal.kind}:${principal.id}`;
|
|
419
|
+
}
|
|
420
|
+
function parseRecord(raw) {
|
|
421
|
+
const value = parseJson(raw);
|
|
422
|
+
if (!isPlainObject(value) || value.v !== 1 || typeof value.taskId !== "string" || typeof value.status !== "string")
|
|
423
|
+
return undefined;
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
426
|
+
function parseJson(raw) {
|
|
427
|
+
try {
|
|
428
|
+
return JSON.parse(raw);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function safeReport(report, error) {
|
|
435
|
+
try {
|
|
436
|
+
report(error);
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
// 보고는 동작을 바꾸지 않는다
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function defaultOnError(error) {
|
|
443
|
+
process.stderr.write(`mext: state store error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
444
|
+
}
|
|
445
|
+
function isPlainObject(value) {
|
|
446
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
447
|
+
}
|
|
448
|
+
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/mcp/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAkB,MAAM,8BAA8B,CAAA;AAC/F,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAKxB,OAAO,EAAE,gBAAgB,EAAkB,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAuB,MAAM,eAAe,CAAA;AAC7E,OAAO,EACL,cAAc,EACd,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,GAIhB,MAAM,WAAW,CAAA;AAElB;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,+BAA+B,CAAA;AAe9D,MAAM,CAAC,MAAM,WAAW,GAAyB,MAAM,CAAC,MAAM,CAAC;IAC7D,cAAc,EAAE,KAAK;IACrB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,KAAK;IAClB,UAAU,EAAE,KAAK;CAClB,CAAC,CAAA;AAEF,sDAAsD;AACtD,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAA;AA+FpC,uCAAuC;AACvC,MAAM,UAAU,eAAe,CAAC,QAAkB;IAChD,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE;QAAE,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;IACnF,OAAO,KAAK,CAAA;AACd,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,aAAa,CAAC,kBAA2B;IACvD,MAAM,UAAU,GAAI,kBAA2D,EAAE,UAAU,CAAA;IAC3F,OAAO,aAAa,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,eAAe,CAAC,KAAK,SAAS,CAAA;AAC/E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAAkB,EAAE,IAAa;IAC5D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,YAAY,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACrG,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;IAC1B,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;IACnE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,4CAA4C,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IACtF,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;IAC3E,MAAM,MAAM,GAAG,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAA;IAC7E,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC5C,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAChF,OAAO,EAAE,KAAK,SAAS,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,IAAI,KAAK,SAAS,CAAA;AAClF,CAAC;AAED,MAAM,SAAS,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,gBAAgB,QAAQ,MAAM,EAAE,CAAA;AACjF,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAE,GAAW,EAAU,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,UAAU,GAAG,EAAE,CAAA;AAC7F,MAAM,SAAS,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAA;AAC3E,MAAM,WAAW,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,gBAAgB,QAAQ,MAAM,EAAE,CAAA;AAEnF,MAAM,QAAQ,GAA4B,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAA;AAavF,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IACpC,MAAM,MAAM,GAAe,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAA;IAChE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAA;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,IAAI,cAAc,CAAA;IAChD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC9C,IAAI,MAAM,GAAG,KAAK,CAAA;IAElB,uCAAuC;IACvC,MAAM,SAAS,GAAG,KAAK,EAAK,EAAoB,EAAc,EAAE;QAC9D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAA;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;YACzB,MAAM,IAAI,aAAa,CAAC,iBAAiB,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAA;QAC/E,CAAC;IACH,CAAC,CAAA;IAED,MAAM,UAAU,GAAG,KAAK,EAAE,MAAc,EAAE,SAAoB,EAAuB,EAAE;QACrF,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QAC/D,MAAM,MAAM,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;QAC/D,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,SAAS,CAAC;YAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;QAC/G,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;IAED,MAAM,IAAI,GAAG,CAAC,MAAkB,EAAW,EAAE,CAC3C,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAA;IAEtF,8DAA8D;IAC9D,MAAM,MAAM,GAAG,KAAK,EAAE,MAAc,EAAE,IAAwB,EAAiB,EAAE;QAC/E,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACjC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QAC3C,IAAI,GAAG;YAAE,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;IAC5F,CAAC,CAAA;IAED,MAAM,KAAK,GAAG,KAAK,EAAE,KAAwB,EAAE,OAA2B,EAA8B,EAAE;QACxG,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;QACpD,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACxC,MAAM,MAAM,GAAe;YACzB,CAAC,EAAE,CAAC;YACJ,MAAM;YACN,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;YACvC,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,OAAO;YAClB,aAAa,EAAE,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,IAAK,CAAC,KAAK;YAChC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;SACxB,CAAA;QACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;QACxC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA+C,CAAA;QACtE,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,IAAI,WAAwB,CAAA;QAC5B,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,WAAW,GAAG,OAAO,CAAC,CAAC,CAAA;QACpE,MAAM,KAAK,GAAG,GAAS,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAA;QAEhF,2CAA2C;QAC3C,IAAI,OAAkC,CAAA;QACtC,IAAI,KAAK,GAAG,KAAK,CAAA;QACjB,MAAM,OAAO,GAAG,GAAkB,EAAE;YAClC,KAAK,GAAG,IAAI,CAAA;YACZ,OAAO,KAAK,CAAC,KAAK,IAAI,EAAE;gBACtB,IAAI,CAAC;oBACH,OAAO,KAAK,EAAE,CAAC;wBACb,KAAK,GAAG,KAAK,CAAA;wBACb,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,WAAW,CAAA;wBAC5F,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;oBACvE,CAAC;gBACH,CAAC;wBAAS,CAAC;oBACT,OAAO,GAAG,SAAS,CAAA;gBACrB,CAAC;YACH,CAAC,CAAC,EAAE,CAAA;YACJ,OAAO,OAAO,CAAA;QAChB,CAAC,CAAA;QACD,MAAM,YAAY,GAAG,GAAS,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;QAEpG,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;YACrC,IAAI,CAAC;gBACH,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;oBACrF,UAAU,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAA;gBACpE,CAAC;gBACD,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC;oBAC1C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;oBAClD,MAAM,MAAM,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;oBAC3E,IAAI,MAAM;wBAAE,OAAO,CAAC,MAAM,CAAC,CAAA;gBAC7B,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;YAC3B,CAAC;QACH,CAAC,CAAA;QAED,IAAI,SAAqD,CAAA;QACzD,IAAI,IAAgD,CAAA;QACpD,IAAI,WAA8C,CAAA;QAClD,MAAM,UAAU,GAAG,GAAS,EAAE;YAC5B,IAAI,SAAS,KAAK,SAAS;gBAAE,aAAa,CAAC,SAAS,CAAC,CAAA;YACrD,IAAI,IAAI,KAAK,SAAS;gBAAE,aAAa,CAAC,IAAI,CAAC,CAAA;QAC7C,CAAC,CAAA;QAED,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;YACvC,IAAI,SAAS;gBAAE,OAAM;YACrB,SAAS,GAAG,IAAI,CAAA;YAChB,UAAU,EAAE,CAAA;YACZ,OAAO,MAAM,CAAC,aAAa,CAAA;YAC3B,KAAK,EAAE,CAAA;YACP,OAAO,CAAC,KAAK,EAAE,CAAA;YACf,IAAI,CAAC;gBACH,MAAM,OAAO,EAAE,CAAA;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;YAC3B,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACtB,IAAI,CAAC;gBACH,MAAM,WAAW,EAAE,EAAE,CAAA;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;YAC3B,CAAC;YACD,WAAW,EAAE,CAAA;QACf,CAAC,CAAA;QAED,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,MAAM,GAAG,GAAe,KAAK,EAAE,UAAU,EAAE,EAAE;YAC3C,MAAM,GAAG,GAAG,GAAG,cAAc,GAAG,KAAK,EAAE,EAAE,CAAA;YACzC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,MAAM,eAAe,CAAC,UAAU,CAAC,CAAA;YAC5G,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAA;YAClC,IAAI,OAAoB,CAAA;YACxB,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAClE,OAAO,GAAG,GAAG,EAAE;oBACb,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;oBACnB,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;gBAClC,CAAC,CAAA;gBACD,UAAU,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;gBACpE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE;oBAC1B,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;oBACnB,UAAU,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;oBACvD,OAAO,CAAC,MAAM,CAAC,CAAA;gBACjB,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;YACF,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACxB,MAAM,CAAC,aAAa,GAAG,EAAE,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,EAAE,CAAA;YAClF,MAAM,CAAC,MAAM,GAAG,gBAAgB,CAAA;YAChC,KAAK,EAAE,CAAA;YACP,IAAI,CAAC;gBACH,MAAM,OAAO,EAAE,CAAA;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;gBACnB,UAAU,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;gBACvD,MAAM,KAAK,CAAA;YACb,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAA;YAC/B,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;gBACzB,OAAO,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;gBAChC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO,MAAM,CAAC,aAAa,CAAA;YACjF,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,aAAa,IAAI,CAAC,SAAS;gBAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAA;YAClE,KAAK,EAAE,CAAA;YACP,IAAI,CAAC,SAAS;gBAAE,YAAY,EAAE,CAAA;YAC9B,OAAO,SAAS,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QACxC,CAAC,CAAA;QAED,MAAM,QAAQ,GAAG,CAAC,MAAsB,EAAQ,EAAE;YAChD,IAAI,SAAS;gBAAE,OAAM;YACrB,MAAM,CAAC,aAAa;gBAClB,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA;YACjH,KAAK,EAAE,CAAA;YACP,YAAY,EAAE,CAAA;QAChB,CAAC,CAAA;QAED,MAAM,IAAI,GAAgB;YACxB,IAAI;YACJ,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;YAC3C,IAAI;YACJ,KAAK,CAAC,WAAW;gBACf,IAAI,SAAS;oBAAE,OAAM;gBACrB,MAAM,CAAC,MAAM,GAAG,WAAW,CAAA;gBAC3B,MAAM,MAAM,EAAE,CAAA;YAChB,CAAC;SACF,CAAA;QACD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QACzB,IAAI,CAAC;YACH,IAAI,GAAG;gBAAE,WAAW,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;YAClF,yCAAyC;YACzC,MAAM,OAAO,EAAE,CAAA;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACtB,MAAM,WAAW,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACrC,MAAM,KAAK,CAAA;QACb,CAAC;QACD,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;YAC3B,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YAC/B,YAAY,EAAE,CAAA;QAChB,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;QACtB,SAAS,CAAC,KAAK,EAAE,EAAE,CAAA;QACnB,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;YACzD,IAAI,CAAC,KAAK,EAAE,EAAE,CAAA;QAChB,CAAC;QACD,OAAO,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAA;QAEzB,MAAM,MAAM,GAAqB;YAC/B,UAAU,EAAE,MAAM;YAClB,MAAM;YACN,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,CAAA;QACD,OAAO;YACL,MAAM;YACN,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,GAAG;YACH,QAAQ;YACR,KAAK,CAAC,MAAM,CAAC,OAAO;gBAClB,IAAI,SAAS;oBAAE,OAAM;gBACrB,IAAI,OAAO,IAAI,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpD,+CAA+C;oBAC/C,MAAM,CAAC,MAAM,GAAG,WAAW,CAAA;gBAC7B,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,MAAM,GAAG,WAAW,CAAA;oBAC3B,MAAM,MAAM,GAAG,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;oBAC3F,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,CAAA;gBAC3E,CAAC;gBACD,MAAM,MAAM,EAAE,CAAA;YAChB,CAAC;SACF,CAAA;IACH,CAAC,CAAA;IAED,OAAO;QACL,MAAM;QACN,OAAO,CAAC,cAAc;YACpB,MAAM,GAAG,GAAwD;gBAC/D,OAAO,EAAE,SAAS;gBAClB,KAAK,CAAC,MAAM,CAAC,KAAK;oBAChB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,MAAM;wBAAE,OAAO,SAAS,CAAA;oBACnD,IAAI,CAAC;wBACH,OAAO,MAAM,KAAK,CAAC,KAAK,EAAE,cAAc,CAAC,CAAA;oBAC3C,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;wBACzB,GAAG,CAAC,OAAO,GAAG,KAAK,CAAA;wBACnB,MAAM,KAAK,CAAA;oBACb,CAAC;gBACH,CAAC;aACF,CAAA;YACD,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS;YACzB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;YAClD,MAAM,IAAI,GAAa;gBACrB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,GAAG,CAAC,MAAM,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtF,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,aAAa,EAAE,MAAM,CAAC,aAAa;gBACnC,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,cAAc,EAAE,MAAM,CAAC,cAAc;aACtC,CAAA;YACD,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAA;gBACtB,IAAI,CAAC,KAAK,GAAG;oBACX,IAAI,EAAE,iBAAiB,CAAC,aAAa;oBACrC,OAAO,EAAE,8DAA8D,MAAM,CAAC,WAAW,MAAM;oBAC/F,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;iBAC1B,CAAA;YACH,CAAC;iBAAM,IAAI,MAAM,CAAC,MAAM,KAAK,gBAAgB,IAAI,MAAM,CAAC,aAAa;gBAAE,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAA;iBAC3G,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM;gBAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;YACpF,OAAO,IAAI,CAAA;QACb,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,cAAc;YAC5C,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;YAClD,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC;gBAAE,OAAM;YACvD,IAAI,KAAK,GAAG,KAAK,CAAA;YACjB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;gBAC1D,kCAAkC;gBAClC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;oBAAE,SAAQ;gBAC/E,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;gBACtE,IAAI,MAAM,KAAK,SAAS;oBAAE,SAAQ;gBAClC,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;gBAC5H,KAAK,GAAG,IAAI,CAAA;YACd,CAAC;YACD,IAAI,KAAK;gBAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC1C,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS;YAC5B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;YAClD,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC;gBAAE,OAAM;YACvD,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;YACvG,MAAM,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAChC,CAAC;QAED,KAAK,CAAC,KAAK;YACT,MAAM,GAAG,IAAI,CAAA;YACb,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;YACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAC9B,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,IAAI,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,sBAAsB,EAAE,YAAY,CAAC,CAAC,CAAA;YAC5F,IAAI,KAAgD,CAAA;YACpD,MAAM,OAAO,CAAC,IAAI,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3C,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;aACjF,CAAC,CAAA;YACF,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;QAC5D,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,OAAoB,EAAE,SAAoB,EAAE,OAAgB;IACjH,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAA;IAC3B,IAAI,OAAO;QAAE,KAAK,CAAC,oBAAoB,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC7C,KAAK,CAAC,iBAAiB,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC7H,yFAAyF;IACzF,KAAK,CAAC,iBAAiB,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE;QAClF,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC,CAAA;QACxE,OAAO,EAAE,CAAA;IACX,CAAC,CAAC,CAAA;IACF,KAAK,CAAC,iBAAiB,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QAC7E,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;QACvC,OAAO,EAAE,CAAA;IACX,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO,IAAI,aAAa,CAAC,iBAAiB,CAAC,aAAa,EAAE,iBAAiB,MAAM,EAAE,CAAC,CAAA;AACtF,CAAC;AAED,SAAS,SAAS,CAAC,SAAoB;IACrC,OAAO,GAAG,SAAS,CAAC,IAAI,IAAI,SAAS,CAAC,EAAE,EAAE,CAAA;AAC5C,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IACpI,OAAO,KAA8B,CAAA;AACvC,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,MAAgC,EAAE,KAAc;IAClE,IAAI,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,CAAA;IACf,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AAC9G,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC","sourcesContent":["import { randomBytes } from \"node:crypto\"\nimport { ProtocolError, ProtocolErrorCode, type McpServer } from \"@modelcontextprotocol/server\"\nimport * as z from \"zod\"\nimport type { ProgressUpdate } from \"../command.ts\"\nimport type { AskHandler, DetachedExecution } from \"../invoke.ts\"\nimport type { Principal } from \"../principal.ts\"\nimport type { RegisteredCommand, Registry } from \"../registry.ts\"\nimport { STATE_KEY_PREFIX, type MextState } from \"../state.ts\"\nimport { errorResult, valueResult, type ToolCallResult } from \"./dispatch.ts\"\nimport {\n ASK_KEY_PREFIX,\n askAnswer,\n elicitation,\n elicitResult,\n modeUnavailable,\n type ElicitationModes,\n type ElicitationRequest,\n type ElicitResultValue,\n} from \"./mrtr.ts\"\n\n/**\n * MCP tasks 확장 (io.modelcontextprotocol/tasks, §12.6). 서버 측: task 생성 (invoke detach) · tasks/get · update · cancel.\n *\n * D5: SDK 2.4.0 은 tasks 런타임이 없다 (task 메서드 = 2025-11-25 어휘, typed API 미제공). 단 3-arg setRequestHandler\n * (custom params schema) 로 tasks/get · update · cancel 을 등록할 수 있고 (era gate 통과), Mcp-Name = params.taskId 헤더 검증은\n * SDK 가 한다. tools/call 결과 resultType \"task\" 는 SDK 가 그대로 통과시킨다. 그래서 mext 가 이 파일에서 직접 구현한다.\n *\n * D2: store 키 (값 = JSON, 접두 \"mext:\", 키마다 writer 하나)\n * mext:task:<taskId> TaskRecord. writer = 실행 프로세스 (생성 · 상태 · heartbeat · 결과)\n * mext:task:<taskId>:input:<ask-N> 답 (ElicitResult). writer = tasks/update 를 받은 프로세스. 이미 있으면 무시\n * mext:task:<taskId>:cancel 취소 기록 ({}). writer = tasks/cancel 을 받은 프로세스\n * bus 채널 mext:task:<taskId> = 답 · 취소 도착 알림 ({\"type\":\"input\"|\"cancel\"}). 내용은 store 가 원본, 메시지는 깨우기만.\n * bus 없음 = 실행 프로세스가 storePollMs (1초) 마다 store 폴링. 같은 프로세스의 task = 직접 깨움.\n * store ttl: 실행 중 = ttlMs + lostAfterMs (heartbeat 마다 연장, 유실 후 failed 를 ttlMs 동안 조회), 종료 = ttlMs.\n *\n * R4: ctx.ask 의미 차이. MRTR (동기) = 라운드마다 핸들러 재실행 (ask 이전 코드 반복), task = 재실행 없이 대기\n * (ask 이전 코드 1회). 키 (ask-0 · ask-1 …) · elicitation 형식 · 답 해석 (askAnswer) · 미선언 mode = INPUT_UNAVAILABLE 은 같다.\n * 두 경로 모두에서 맞게 동작하려면 MRTR 규칙 (부작용은 ask 뒤) 을 따른다.\n */\n\nexport const TASKS_EXTENSION = \"io.modelcontextprotocol/tasks\"\n\nexport interface TaskTiming {\n /** CreateTaskResult · tasks/get 의 pollIntervalMs (클라이언트 폴링 권장 간격) */\n pollIntervalMs: number\n /** 실행 프로세스의 heartbeat (레코드 갱신) 간격 */\n heartbeatMs: number\n /** heartbeat 이 이보다 오래 없으면 tasks/get = failed (TASK_LOST) */\n lostAfterMs: number\n /** bus 없음: 실행 프로세스의 답 · 취소 store 폴링 간격 */\n storePollMs: number\n /** close (SIGTERM · SIGINT) 시 실행 중 task 정리 상한 */\n shutdownMs: number\n}\n\nexport const TASK_TIMING: Readonly<TaskTiming> = Object.freeze({\n pollIntervalMs: 2_000,\n heartbeatMs: 10_000,\n lostAfterMs: 30_000,\n storePollMs: 1_000,\n shutdownMs: 5_000,\n})\n\n/** tasks/get failed 의 error.data.code (실행 프로세스 유실) */\nexport const TASK_LOST = \"TASK_LOST\"\n\nexport type TaskStatus = \"working\" | \"input_required\" | \"completed\" | \"failed\" | \"cancelled\"\n\n/** tools/call 의 task 응답 (resultType \"task\") */\nexport interface CreateTaskResult {\n resultType: \"task\"\n taskId: string\n status: TaskStatus\n statusMessage?: string\n createdAt: string\n lastUpdatedAt: string\n ttlMs: number\n pollIntervalMs: number\n _meta?: Record<string, unknown>\n}\n\n/** tasks/get 결과 (resultType 은 SDK 가 \"complete\" 로 채운다) */\nexport interface TaskView {\n taskId: string\n status: TaskStatus\n statusMessage?: string\n createdAt: string\n lastUpdatedAt: string\n ttlMs: number\n pollIntervalMs: number\n /** input_required */\n inputRequests?: Record<string, ElicitationRequest>\n /** completed = 같은 요청의 동기 tools/call 결과 */\n result?: TaskResult\n /** failed (TASK_LOST) */\n error?: TaskError\n}\n\ntype TaskResult = ToolCallResult & { resultType: \"complete\"; _meta?: Record<string, unknown> }\n\ninterface TaskError {\n code: number\n message: string\n data?: Record<string, unknown>\n}\n\n/** D2: mext:task:<taskId> 의 값 */\ninterface TaskRecord {\n v: 1\n taskId: string\n /** 바인딩 \"<kind>:<id>\". 다른 주체 = 없는 task */\n principal: string\n command: string\n status: TaskStatus\n statusMessage?: string\n createdAt: string\n lastUpdatedAt: string\n ttlMs: number\n /** epoch ms. heartbeatMs 마다 갱신 */\n heartbeatAt: number\n inputRequests?: Record<string, ElicitationRequest>\n result?: TaskResult\n}\n\nexport interface TaskRuntimeOptions {\n state: MextState\n timing?: Partial<TaskTiming>\n /** 완료 결과 _meta (동기 결과와 같은 manifestHash) */\n meta?: Record<string, unknown>\n /** store · bus 예외 보고. 기본 stderr 1줄 */\n onerror?: (error: unknown) => void\n}\n\n/** 요청 하나의 task 조건 */\nexport interface TaskRequestOptions {\n principal: Principal\n /** 생성 요청이 선언한 elicitation mode (ctx.ask 가능 여부, MRTR 과 같은 규칙) */\n modes: ElicitationModes\n /** handler.js: 응답 후 실행 유지 (플랫폼 waitUntil) */\n waitUntil?: (promise: Promise<unknown>) => void\n}\n\nexport interface TaskRequest {\n /** invoke 의 detach. task 명령어가 아니면 undefined (동기) */\n detach(entry: RegisteredCommand): Promise<DetachedExecution | undefined>\n /** 생성 중 store · bus 예외 (있으면 요청 = -32603) */\n readonly failure: unknown\n}\n\nexport interface TaskRuntime {\n readonly timing: TaskTiming\n request(options: TaskRequestOptions): TaskRequest\n get(taskId: string, principal: Principal): Promise<TaskView>\n update(taskId: string, principal: Principal, inputResponses: Record<string, unknown>): Promise<void>\n cancel(taskId: string, principal: Principal): Promise<void>\n /** 실행 중 task 취소 → cancelled 기록 (상한 shutdownMs). 이후 새 task 없음 (동기 실행) */\n close(): Promise<void>\n}\n\n/** registry 에 task 명령어가 있는지 (선언 조건) */\nexport function hasTaskCommands(registry: Registry): boolean {\n for (const entry of registry.commands.values()) if (entry.command.task) return true\n return false\n}\n\n/** 요청 _meta 의 clientCapabilities 에 tasks 확장 선언 */\nexport function declaresTasks(clientCapabilities: unknown): boolean {\n const extensions = (clientCapabilities as { extensions?: unknown } | undefined)?.extensions\n return isPlainObject(extensions) && extensions[TASKS_EXTENSION] !== undefined\n}\n\n/**\n * HTTP: task 가 될 수 있는 tools/call 인지 (응답 = application/json, §12.6). 확장 선언 + 대상 명령어가 task 명령어.\n * 대상 = dispatcher 의 arguments.command (별칭 포함), 그 외 = 도구 이름 (command 노출)\n */\nexport function requestsTask(registry: Registry, body: unknown): boolean {\n if (!isPlainObject(body) || body.method !== \"tools/call\" || !isPlainObject(body.params)) return false\n const params = body.params\n const meta = isPlainObject(params._meta) ? params._meta : undefined\n if (!declaresTasks(meta?.[\"io.modelcontextprotocol/clientCapabilities\"])) return false\n const args = isPlainObject(params.arguments) ? params.arguments : undefined\n const target = typeof args?.command === \"string\" ? args.command : params.name\n if (typeof target !== \"string\") return false\n const id = registry.commands.has(target) ? target : registry.aliases.get(target)\n return id !== undefined && registry.commands.get(id)?.command.task !== undefined\n}\n\nconst recordKey = (taskId: string): string => `${STATE_KEY_PREFIX}task:${taskId}`\nconst inputKey = (taskId: string, key: string): string => `${recordKey(taskId)}:input:${key}`\nconst cancelKey = (taskId: string): string => `${recordKey(taskId)}:cancel`\nconst taskChannel = (taskId: string): string => `${STATE_KEY_PREFIX}task:${taskId}`\n\nconst TERMINAL: ReadonlySet<TaskStatus> = new Set([\"completed\", \"failed\", \"cancelled\"])\n\n/** 같은 프로세스에서 실행 중인 task */\ninterface RunningTask {\n /** 답 · 취소를 store 에서 다시 읽는다 */\n wake(): Promise<void>\n abort(reason: unknown): void\n /** 종료 기록 완료 */\n done: Promise<void>\n /** 아직 끝나지 않았으면 cancelled 기록 (종료 상한) */\n forceCancel(): Promise<void>\n}\n\nexport function createTaskRuntime(options: TaskRuntimeOptions): TaskRuntime {\n const { store, bus } = options.state\n const timing: TaskTiming = { ...TASK_TIMING, ...options.timing }\n const meta = options.meta ?? {}\n const report = options.onerror ?? defaultOnError\n const running = new Map<string, RunningTask>()\n let closed = false\n\n /** store · bus 호출. 예외 = 보고 + -32603 */\n const storeCall = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (error) {\n safeReport(report, error)\n throw new ProtocolError(ProtocolErrorCode.InternalError, \"State store error\")\n }\n }\n\n const readRecord = async (taskId: string, principal: Principal): Promise<TaskRecord> => {\n const raw = await storeCall(() => store.get(recordKey(taskId)))\n const record = raw === undefined ? undefined : parseRecord(raw)\n if (!record || record.taskId !== taskId || record.principal !== bindingOf(principal)) throw unknownTask(taskId)\n return record\n }\n\n const lost = (record: TaskRecord): boolean =>\n !TERMINAL.has(record.status) && Date.now() - record.heartbeatAt > timing.lostAfterMs\n\n /** 답 · 취소 도착 알림: 같은 프로세스 = 직접, 그 외 = bus (없으면 실행 프로세스가 폴링) */\n const notify = async (taskId: string, type: \"input\" | \"cancel\"): Promise<void> => {\n const local = running.get(taskId)\n if (local) return void (await local.wake())\n if (bus) await storeCall(() => bus.publish(taskChannel(taskId), JSON.stringify({ type })))\n }\n\n const start = async (entry: RegisteredCommand, request: TaskRequestOptions): Promise<DetachedExecution> => {\n const taskId = randomBytes(16).toString(\"base64url\")\n const created = new Date().toISOString()\n const record: TaskRecord = {\n v: 1,\n taskId,\n principal: bindingOf(request.principal),\n command: entry.id,\n status: \"working\",\n createdAt: created,\n lastUpdatedAt: created,\n ttlMs: entry.command.task!.ttlMs,\n heartbeatAt: Date.now(),\n }\n const controller = new AbortController()\n const waiters = new Map<string, (answer: ElicitResultValue) => void>()\n let finalized = false\n let resolveDone!: () => void\n const done = new Promise<void>((resolve) => (resolveDone = resolve))\n const touch = (): void => void (record.lastUpdatedAt = new Date().toISOString())\n\n // 순서 보장 · 합치기: 쓰는 중 변경 = 끝난 뒤 최신 상태로 한 번 더\n let writing: Promise<void> | undefined\n let dirty = false\n const persist = (): Promise<void> => {\n dirty = true\n writing ??= (async () => {\n try {\n while (dirty) {\n dirty = false\n const ttlMs = TERMINAL.has(record.status) ? record.ttlMs : record.ttlMs + timing.lostAfterMs\n await store.set(recordKey(taskId), JSON.stringify(record), { ttlMs })\n }\n } finally {\n writing = undefined\n }\n })()\n return writing\n }\n const persistLater = (): void => void persist().catch((error: unknown) => safeReport(report, error))\n\n const wake = async (): Promise<void> => {\n try {\n if (!controller.signal.aborted && (await store.get(cancelKey(taskId))) !== undefined) {\n controller.abort(new DOMException(\"Task cancelled\", \"AbortError\"))\n }\n for (const [key, resolve] of [...waiters]) {\n const raw = await store.get(inputKey(taskId, key))\n const answer = raw === undefined ? undefined : elicitResult(parseJson(raw))\n if (answer) resolve(answer)\n }\n } catch (error) {\n safeReport(report, error)\n }\n }\n\n let heartbeat: ReturnType<typeof setInterval> | undefined\n let poll: ReturnType<typeof setInterval> | undefined\n let unsubscribe: (() => Promise<void>) | undefined\n const stopTimers = (): void => {\n if (heartbeat !== undefined) clearInterval(heartbeat)\n if (poll !== undefined) clearInterval(poll)\n }\n\n const finish = async (): Promise<void> => {\n if (finalized) return\n finalized = true\n stopTimers()\n delete record.inputRequests\n touch()\n waiters.clear()\n try {\n await persist()\n } catch (error) {\n safeReport(report, error)\n }\n running.delete(taskId)\n try {\n await unsubscribe?.()\n } catch (error) {\n safeReport(report, error)\n }\n resolveDone()\n }\n\n let index = 0\n const ask: AskHandler = async (askRequest) => {\n const key = `${ASK_KEY_PREFIX}${index++}`\n if (!(askRequest.kind === \"url\" ? request.modes.url : request.modes.form)) throw modeUnavailable(askRequest)\n controller.signal.throwIfAborted()\n let onAbort!: () => void\n const answered = new Promise<ElicitResultValue>((resolve, reject) => {\n onAbort = () => {\n waiters.delete(key)\n reject(controller.signal.reason)\n }\n controller.signal.addEventListener(\"abort\", onAbort, { once: true })\n waiters.set(key, (answer) => {\n waiters.delete(key)\n controller.signal.removeEventListener(\"abort\", onAbort)\n resolve(answer)\n })\n })\n answered.catch(() => {})\n record.inputRequests = { ...record.inputRequests, [key]: elicitation(askRequest) }\n record.status = \"input_required\"\n touch()\n try {\n await persist()\n } catch (error) {\n waiters.delete(key)\n controller.signal.removeEventListener(\"abort\", onAbort)\n throw error\n }\n const response = await answered\n if (record.inputRequests) {\n delete record.inputRequests[key]\n if (Object.keys(record.inputRequests).length === 0) delete record.inputRequests\n }\n if (!record.inputRequests && !finalized) record.status = \"working\"\n touch()\n if (!finalized) persistLater()\n return askAnswer(askRequest, response)\n }\n\n const progress = (update: ProgressUpdate): void => {\n if (finalized) return\n record.statusMessage =\n update.message ?? (update.total !== undefined ? `${update.progress}/${update.total}` : String(update.progress))\n touch()\n persistLater()\n }\n\n const task: RunningTask = {\n wake,\n abort: (reason) => controller.abort(reason),\n done,\n async forceCancel() {\n if (finalized) return\n record.status = \"cancelled\"\n await finish()\n },\n }\n running.set(taskId, task)\n try {\n if (bus) unsubscribe = await bus.subscribe(taskChannel(taskId), () => void wake())\n // store 기록 후 응답 (tasks/get 이 바로 성공해야 한다)\n await persist()\n } catch (error) {\n running.delete(taskId)\n await unsubscribe?.().catch(() => {})\n throw error\n }\n heartbeat = setInterval(() => {\n record.heartbeatAt = Date.now()\n persistLater()\n }, timing.heartbeatMs)\n heartbeat.unref?.()\n if (!bus) {\n poll = setInterval(() => void wake(), timing.storePollMs)\n poll.unref?.()\n }\n request.waitUntil?.(done)\n\n const handle: CreateTaskResult = {\n resultType: \"task\",\n taskId,\n status: \"working\",\n createdAt: record.createdAt,\n lastUpdatedAt: record.lastUpdatedAt,\n ttlMs: record.ttlMs,\n pollIntervalMs: timing.pollIntervalMs,\n }\n return {\n handle,\n signal: controller.signal,\n ask,\n progress,\n async settle(outcome) {\n if (finalized) return\n if (\"error\" in outcome && controller.signal.aborted) {\n // 취소 후 예외 = cancelled. 핸들러가 끝까지 가면 (값 반환) 그 결과\n record.status = \"cancelled\"\n } else {\n record.status = \"completed\"\n const result = \"error\" in outcome ? errorResult(outcome.error) : valueResult(outcome.value)\n record.result = { ...result, resultType: \"complete\", _meta: { ...meta } }\n }\n await finish()\n },\n }\n }\n\n return {\n timing,\n request(requestOptions) {\n const out: { failure: unknown; detach: TaskRequest[\"detach\"] } = {\n failure: undefined,\n async detach(entry) {\n if (!entry.command.task || closed) return undefined\n try {\n return await start(entry, requestOptions)\n } catch (error) {\n safeReport(report, error)\n out.failure = error\n throw error\n }\n },\n }\n return out\n },\n\n async get(taskId, principal) {\n const record = await readRecord(taskId, principal)\n const view: TaskView = {\n taskId: record.taskId,\n status: record.status,\n ...(record.statusMessage !== undefined ? { statusMessage: record.statusMessage } : {}),\n createdAt: record.createdAt,\n lastUpdatedAt: record.lastUpdatedAt,\n ttlMs: record.ttlMs,\n pollIntervalMs: timing.pollIntervalMs,\n }\n if (lost(record)) {\n view.status = \"failed\"\n view.error = {\n code: ProtocolErrorCode.InternalError,\n message: `Task lost: the executing process stopped (no heartbeat for ${timing.lostAfterMs}ms).`,\n data: { code: TASK_LOST },\n }\n } else if (record.status === \"input_required\" && record.inputRequests) view.inputRequests = record.inputRequests\n else if (record.status === \"completed\" && record.result) view.result = record.result\n return view\n },\n\n async update(taskId, principal, inputResponses) {\n const record = await readRecord(taskId, principal)\n if (TERMINAL.has(record.status) || lost(record)) return\n let wrote = false\n for (const [key, value] of Object.entries(inputResponses)) {\n // 모르는 키 · 형식이 아닌 답 · 이미 답한 키 = 무시\n if (record.inputRequests?.[key] === undefined || !elicitResult(value)) continue\n const stored = await storeCall(() => store.get(inputKey(taskId, key)))\n if (stored !== undefined) continue\n await storeCall(() => store.set(inputKey(taskId, key), JSON.stringify(value), { ttlMs: record.ttlMs + timing.lostAfterMs }))\n wrote = true\n }\n if (wrote) await notify(taskId, \"input\")\n },\n\n async cancel(taskId, principal) {\n const record = await readRecord(taskId, principal)\n if (TERMINAL.has(record.status) || lost(record)) return\n await storeCall(() => store.set(cancelKey(taskId), \"{}\", { ttlMs: record.ttlMs + timing.lostAfterMs }))\n await notify(taskId, \"cancel\")\n },\n\n async close() {\n closed = true\n const tasks = [...running.values()]\n if (tasks.length === 0) return\n for (const task of tasks) task.abort(new DOMException(\"Server shutting down\", \"AbortError\"))\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([\n Promise.all(tasks.map((task) => task.done)),\n new Promise<void>((resolve) => (timer = setTimeout(resolve, timing.shutdownMs))),\n ])\n clearTimeout(timer)\n await Promise.all(tasks.map((task) => task.forceCancel()))\n },\n }\n}\n\n/**\n * McpServer 에 tasks/get · tasks/update · tasks/cancel 등록 (D5: 3-arg setRequestHandler, custom params schema).\n * declare = server/discover capabilities.extensions 에 확장 선언 (생성 가능할 때만). transport 연결 전에 호출\n */\nexport function installTaskHandlers(server: McpServer, runtime: TaskRuntime, principal: Principal, declare: boolean): void {\n const inner = server.server\n if (declare) inner.registerCapabilities({ extensions: { [TASKS_EXTENSION]: {} } })\n const byId = z.object({ taskId: z.string() })\n inner.setRequestHandler(\"tasks/get\", { params: byId }, async ({ taskId }) => ({ ...(await runtime.get(taskId, principal)) }))\n // inputResponses 는 SDK 가 params 에서 분리 (MRTR 예약 키) → ctx.mcpReq.inputResponses (bare 결과만)\n inner.setRequestHandler(\"tasks/update\", { params: byId }, async ({ taskId }, ctx) => {\n await runtime.update(taskId, principal, ctx.mcpReq.inputResponses ?? {})\n return {}\n })\n inner.setRequestHandler(\"tasks/cancel\", { params: byId }, async ({ taskId }) => {\n await runtime.cancel(taskId, principal)\n return {}\n })\n}\n\nfunction unknownTask(taskId: string): ProtocolError {\n return new ProtocolError(ProtocolErrorCode.InvalidParams, `Unknown task: ${taskId}`)\n}\n\nfunction bindingOf(principal: Principal): string {\n return `${principal.kind}:${principal.id}`\n}\n\nfunction parseRecord(raw: string): TaskRecord | undefined {\n const value = parseJson(raw)\n if (!isPlainObject(value) || value.v !== 1 || typeof value.taskId !== \"string\" || typeof value.status !== \"string\") return undefined\n return value as unknown as TaskRecord\n}\n\nfunction parseJson(raw: string): unknown {\n try {\n return JSON.parse(raw)\n } catch {\n return undefined\n }\n}\n\nfunction safeReport(report: (error: unknown) => void, error: unknown): void {\n try {\n report(error)\n } catch {\n // 보고는 동작을 바꾸지 않는다\n }\n}\n\nfunction defaultOnError(error: unknown): void {\n process.stderr.write(`mext: state store error: ${error instanceof Error ? error.message : String(error)}\\n`)\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BuildIssue, BuildIssueCode } from "./errors.ts";
|
|
2
|
+
/**
|
|
3
|
+
* commands/ 외 스캔 폴더 (resources/ · prompts/) 의 공통 파일 규칙 (§12.3 "공통 = §3").
|
|
4
|
+
* private (`_` · `.` 접두) · 제외 (*.test.ts · *.spec.ts · *.d.ts) · 폴더 · 파일 kebab-case.
|
|
5
|
+
* 폴더 없음 = 기능 없음 (issue 아님). 식별자 규칙 (중복 등) 은 호출자 몫
|
|
6
|
+
*/
|
|
7
|
+
export interface ModuleFile {
|
|
8
|
+
/** 폴더 기준 상대 경로 (posix) */
|
|
9
|
+
file: string;
|
|
10
|
+
/** 경로 세그먼트 (.ts 제외, index 포함) */
|
|
11
|
+
segments: string[];
|
|
12
|
+
}
|
|
13
|
+
/** label = issue 의 file 접두 (폴더 이름) */
|
|
14
|
+
export declare function walkModuleFiles(dir: string, label: string): {
|
|
15
|
+
files: ModuleFile[];
|
|
16
|
+
issues: BuildIssue[];
|
|
17
|
+
};
|
|
18
|
+
/** 각 파일의 default export import. import 실패 = LOAD_FAILED, guard 불통과 = invalidCode */
|
|
19
|
+
export declare function importModuleDefaults<T>(dir: string, files: readonly ModuleFile[], label: string, guard: (value: unknown) => value is T, invalidCode: BuildIssueCode, definer: string): Promise<{
|
|
20
|
+
modules: (ModuleFile & {
|
|
21
|
+
value: T;
|
|
22
|
+
})[];
|
|
23
|
+
issues: BuildIssue[];
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { readdirSync, statSync } from "node:fs";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { isSegment } from "./ids.js";
|
|
13
|
+
const EXCLUDED_SUFFIXES = [".test.ts", ".spec.ts", ".d.ts"];
|
|
14
|
+
/** label = issue 의 file 접두 (폴더 이름) */
|
|
15
|
+
export function walkModuleFiles(dir, label) {
|
|
16
|
+
const root = resolve(dir);
|
|
17
|
+
const files = [];
|
|
18
|
+
const issues = [];
|
|
19
|
+
if (!isDirectory(root))
|
|
20
|
+
return { files, issues };
|
|
21
|
+
const visit = (abs, segments) => {
|
|
22
|
+
const entries = readdirSync(abs, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const name = entry.name;
|
|
25
|
+
if (name.startsWith("_") || name.startsWith("."))
|
|
26
|
+
continue;
|
|
27
|
+
const rel = [...segments, name].join("/");
|
|
28
|
+
const childAbs = join(abs, name);
|
|
29
|
+
if (isDirEntry(abs, entry)) {
|
|
30
|
+
if (!isSegment(name)) {
|
|
31
|
+
issues.push({ code: "INVALID_SEGMENT", message: `directory "${name}" is not kebab-case`, file: `${label}/${rel}` });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
visit(childAbs, [...segments, name]);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!name.endsWith(".ts") || EXCLUDED_SUFFIXES.some((s) => name.endsWith(s)))
|
|
38
|
+
continue;
|
|
39
|
+
if (!isFileEntry(abs, entry))
|
|
40
|
+
continue;
|
|
41
|
+
const base = name.slice(0, -".ts".length);
|
|
42
|
+
if (!isSegment(base)) {
|
|
43
|
+
issues.push({ code: "INVALID_SEGMENT", message: `file "${name}" is not kebab-case`, file: `${label}/${rel}` });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
files.push({ file: rel, segments: [...segments, base] });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
visit(root, []);
|
|
50
|
+
return { files, issues };
|
|
51
|
+
}
|
|
52
|
+
/** 각 파일의 default export import. import 실패 = LOAD_FAILED, guard 불통과 = invalidCode */
|
|
53
|
+
export async function importModuleDefaults(dir, files, label, guard, invalidCode, definer) {
|
|
54
|
+
const root = resolve(dir);
|
|
55
|
+
const modules = [];
|
|
56
|
+
const issues = [];
|
|
57
|
+
for (const entry of files) {
|
|
58
|
+
let mod;
|
|
59
|
+
try {
|
|
60
|
+
mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(join(root, entry.file)).href)));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
issues.push({ code: "LOAD_FAILED", message: error instanceof Error ? error.message : String(error), file: `${label}/${entry.file}` });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!guard(mod.default)) {
|
|
67
|
+
issues.push({ code: invalidCode, message: `default export is not a ${definer} result`, file: `${label}/${entry.file}` });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
modules.push({ ...entry, value: mod.default });
|
|
71
|
+
}
|
|
72
|
+
return { modules, issues };
|
|
73
|
+
}
|
|
74
|
+
function isDirectory(path) {
|
|
75
|
+
try {
|
|
76
|
+
return statSync(path).isDirectory();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function isDirEntry(parent, entry) {
|
|
83
|
+
return entry.isDirectory() || (entry.isSymbolicLink() && isDirectory(join(parent, entry.name)));
|
|
84
|
+
}
|
|
85
|
+
function isFileEntry(parent, entry) {
|
|
86
|
+
if (entry.isFile())
|
|
87
|
+
return true;
|
|
88
|
+
if (!entry.isSymbolicLink())
|
|
89
|
+
return false;
|
|
90
|
+
try {
|
|
91
|
+
return statSync(join(parent, entry.name)).isFile();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=module-files.js.map
|