@obov/mext 0.5.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/approval.d.ts +33 -0
  2. package/dist/approval.js +36 -0
  3. package/dist/approval.js.map +1 -0
  4. package/dist/audit.d.ts +123 -0
  5. package/dist/audit.js +221 -0
  6. package/dist/audit.js.map +1 -0
  7. package/dist/build-cli.d.ts +1 -0
  8. package/dist/build-cli.js.map +1 -1
  9. package/dist/build.d.ts +2 -0
  10. package/dist/build.js +46 -5
  11. package/dist/build.js.map +1 -1
  12. package/dist/caller.d.ts +53 -0
  13. package/dist/caller.js +51 -0
  14. package/dist/caller.js.map +1 -0
  15. package/dist/cli/args.js +1 -2
  16. package/dist/cli/args.js.map +1 -1
  17. package/dist/cli/build.js +26 -4
  18. package/dist/cli/build.js.map +1 -1
  19. package/dist/cli/dev-audit.d.ts +25 -0
  20. package/dist/cli/dev-audit.js +84 -0
  21. package/dist/cli/dev-audit.js.map +1 -0
  22. package/dist/cli/dev-lock.d.ts +3 -1
  23. package/dist/cli/dev-lock.js +12 -5
  24. package/dist/cli/dev-lock.js.map +1 -1
  25. package/dist/cli/dev-server.d.ts +9 -0
  26. package/dist/cli/dev-server.js +27 -3
  27. package/dist/cli/dev-server.js.map +1 -1
  28. package/dist/cli/dev-tap.d.ts +20 -0
  29. package/dist/cli/dev-tap.js +70 -0
  30. package/dist/cli/dev-tap.js.map +1 -0
  31. package/dist/cli/dev.d.ts +6 -0
  32. package/dist/cli/dev.js +20 -8
  33. package/dist/cli/dev.js.map +1 -1
  34. package/dist/cli/help.js +2 -0
  35. package/dist/cli/help.js.map +1 -1
  36. package/dist/cli/inspect.d.ts +7 -0
  37. package/dist/cli/inspect.js +699 -0
  38. package/dist/cli/inspect.js.map +1 -0
  39. package/dist/cli/main.js +10 -0
  40. package/dist/cli/main.js.map +1 -1
  41. package/dist/cli/media.d.ts +19 -0
  42. package/dist/cli/media.js +77 -0
  43. package/dist/cli/media.js.map +1 -0
  44. package/dist/cli/output.d.ts +1 -1
  45. package/dist/cli/output.js +3 -2
  46. package/dist/cli/output.js.map +1 -1
  47. package/dist/cli/run.js +33 -4
  48. package/dist/cli/run.js.map +1 -1
  49. package/dist/cli/serve.d.ts +10 -0
  50. package/dist/cli/serve.js +114 -0
  51. package/dist/cli/serve.js.map +1 -0
  52. package/dist/cli/types.d.ts +9 -0
  53. package/dist/cli/types.js +77 -0
  54. package/dist/cli/types.js.map +1 -0
  55. package/dist/client/index.d.ts +11 -0
  56. package/dist/client/index.js +7 -0
  57. package/dist/client/index.js.map +1 -0
  58. package/dist/client/manifest.js +6 -2
  59. package/dist/client/manifest.js.map +1 -1
  60. package/dist/client/runner.js +40 -3
  61. package/dist/client/runner.js.map +1 -1
  62. package/dist/client/typed.d.ts +56 -0
  63. package/dist/client/typed.js +526 -0
  64. package/dist/client/typed.js.map +1 -0
  65. package/dist/client/types.d.ts +225 -0
  66. package/dist/client/types.js +2 -0
  67. package/dist/client/types.js.map +1 -0
  68. package/dist/command.d.ts +61 -5
  69. package/dist/command.js +42 -1
  70. package/dist/command.js.map +1 -1
  71. package/dist/config.d.ts +59 -10
  72. package/dist/config.js +67 -7
  73. package/dist/config.js.map +1 -1
  74. package/dist/content.d.ts +65 -0
  75. package/dist/content.js +99 -0
  76. package/dist/content.js.map +1 -0
  77. package/dist/errors.d.ts +21 -3
  78. package/dist/errors.js +13 -0
  79. package/dist/errors.js.map +1 -1
  80. package/dist/expose.d.ts +9 -0
  81. package/dist/expose.js +52 -0
  82. package/dist/expose.js.map +1 -0
  83. package/dist/index.d.ts +18 -7
  84. package/dist/index.js +8 -2
  85. package/dist/index.js.map +1 -1
  86. package/dist/inspect/agent.d.ts +104 -0
  87. package/dist/inspect/agent.js +125 -0
  88. package/dist/inspect/agent.js.map +1 -0
  89. package/dist/inspect/audit.d.ts +46 -0
  90. package/dist/inspect/audit.js +133 -0
  91. package/dist/inspect/audit.js.map +1 -0
  92. package/dist/inspect/compare.d.ts +12 -0
  93. package/dist/inspect/compare.js +31 -0
  94. package/dist/inspect/compare.js.map +1 -0
  95. package/dist/inspect/index.d.ts +17 -0
  96. package/dist/inspect/index.js +12 -0
  97. package/dist/inspect/index.js.map +1 -0
  98. package/dist/inspect/inspector.d.ts +168 -0
  99. package/dist/inspect/inspector.js +392 -0
  100. package/dist/inspect/inspector.js.map +1 -0
  101. package/dist/inspect/profiles.d.ts +95 -0
  102. package/dist/inspect/profiles.js +235 -0
  103. package/dist/inspect/profiles.js.map +1 -0
  104. package/dist/inspect/scenario.d.ts +126 -0
  105. package/dist/inspect/scenario.js +555 -0
  106. package/dist/inspect/scenario.js.map +1 -0
  107. package/dist/inspect/ui-server.d.ts +31 -0
  108. package/dist/inspect/ui-server.js +378 -0
  109. package/dist/inspect/ui-server.js.map +1 -0
  110. package/dist/inspect/ui.html +917 -0
  111. package/dist/instrumentation.d.ts +13 -2
  112. package/dist/instrumentation.js +11 -1
  113. package/dist/instrumentation.js.map +1 -1
  114. package/dist/invoke.d.ts +36 -5
  115. package/dist/invoke.js +162 -21
  116. package/dist/invoke.js.map +1 -1
  117. package/dist/manifest.d.ts +19 -3
  118. package/dist/manifest.js +5 -1
  119. package/dist/manifest.js.map +1 -1
  120. package/dist/mcp/constants.d.ts +7 -0
  121. package/dist/mcp/constants.js +7 -0
  122. package/dist/mcp/constants.js.map +1 -1
  123. package/dist/mcp/dispatch.d.ts +10 -4
  124. package/dist/mcp/dispatch.js +24 -7
  125. package/dist/mcp/dispatch.js.map +1 -1
  126. package/dist/mcp/help.d.ts +2 -0
  127. package/dist/mcp/help.js +6 -1
  128. package/dist/mcp/help.js.map +1 -1
  129. package/dist/mcp/host.d.ts +14 -0
  130. package/dist/mcp/host.js +48 -0
  131. package/dist/mcp/host.js.map +1 -0
  132. package/dist/mcp/http.d.ts +38 -7
  133. package/dist/mcp/http.js +199 -23
  134. package/dist/mcp/http.js.map +1 -1
  135. package/dist/mcp/index.d.ts +2 -1
  136. package/dist/mcp/index.js +2 -1
  137. package/dist/mcp/index.js.map +1 -1
  138. package/dist/mcp/server.d.ts +12 -1
  139. package/dist/mcp/server.js +63 -17
  140. package/dist/mcp/server.js.map +1 -1
  141. package/dist/mcp/stdio.d.ts +4 -0
  142. package/dist/mcp/stdio.js +3 -0
  143. package/dist/mcp/stdio.js.map +1 -1
  144. package/dist/mcp/tasks.d.ts +3 -1
  145. package/dist/mcp/tasks.js +7 -4
  146. package/dist/mcp/tasks.js.map +1 -1
  147. package/dist/principal.d.ts +22 -0
  148. package/dist/principal.js +29 -0
  149. package/dist/principal.js.map +1 -1
  150. package/dist/registry.d.ts +5 -0
  151. package/dist/registry.js +21 -0
  152. package/dist/registry.js.map +1 -1
  153. package/dist/remote/client.d.ts +26 -1
  154. package/dist/remote/client.js +180 -55
  155. package/dist/remote/client.js.map +1 -1
  156. package/dist/remote/index.d.ts +1 -1
  157. package/dist/remote/index.js.map +1 -1
  158. package/dist/remote/oauth.d.ts +1 -1
  159. package/dist/remote/oauth.js +1 -1
  160. package/dist/remote/oauth.js.map +1 -1
  161. package/dist/route.d.ts +40 -2
  162. package/dist/route.js +128 -8
  163. package/dist/route.js.map +1 -1
  164. package/dist/start.js +9 -4
  165. package/dist/start.js.map +1 -1
  166. package/dist/suggest.js +1 -1
  167. package/dist/suggest.js.map +1 -1
  168. package/dist/typegen.d.ts +27 -0
  169. package/dist/typegen.js +62 -0
  170. package/dist/typegen.js.map +1 -0
  171. package/dist/version-constant.d.ts +5 -0
  172. package/dist/version-constant.js +6 -0
  173. package/dist/version-constant.js.map +1 -0
  174. package/dist/version.d.ts +1 -2
  175. package/dist/version.js +1 -2
  176. package/dist/version.js.map +1 -1
  177. package/package.json +12 -2
@@ -61,6 +61,11 @@ interface OtelTracer {
61
61
  }, context: unknown, fn: (span: OtelSpan) => T): T;
62
62
  }
63
63
  interface OtelSpan {
64
+ /** 없는 구현 (테스트 대역) 도 허용 */
65
+ spanContext?(): {
66
+ traceId?: unknown;
67
+ spanId?: unknown;
68
+ } | undefined;
64
69
  setAttribute(key: string, value: string): unknown;
65
70
  setStatus(status: {
66
71
  code: number;
@@ -79,6 +84,11 @@ export declare function loadProjectInstrumentation(cwd?: string, options?: LoadI
79
84
  * source 없음 = hooks 없이 span 만. 빌드 산출물 (.mext/server/instrumentation.js) 도 같은 함수.
80
85
  */
81
86
  export declare function loadInstrumentation(source?: string | URL | InstrumentationModule, options?: LoadInstrumentationOptions): Promise<Instrumentation>;
87
+ /** span 식별자 (감사 이벤트의 traceId · spanId, §11.1). 유효하지 않은 (전부 0) 값은 넘기지 않는다 */
88
+ export interface TraceIds {
89
+ traceId: string;
90
+ spanId?: string;
91
+ }
82
92
  export interface ObserveOptions {
83
93
  instrumentation?: Instrumentation;
84
94
  transport?: Transport;
@@ -91,13 +101,14 @@ export interface ObserveOptions {
91
101
  * 속성 = mext.command.id · mext.transport · mext.principal.kind. 인자 · 결과 · 토큰은 기록하지 않는다.
92
102
  * 실패: status ERROR + mext.error.code. 핸들러 예외만 recordException (MextError · CommandError 메시지는 입력 일부를 담을 수 있어 제외).
93
103
  * 취소 (signal abort · MRTR 대기) = status 없음.
104
+ * fn 인자 = span 의 traceId · spanId (span 없음 = undefined)
94
105
  */
95
- export declare function observeCommand<T>(command: string, options: ObserveOptions, signal: () => AbortSignal | undefined, fn: () => Promise<T>): Promise<T>;
106
+ export declare function observeCommand<T>(command: string, options: ObserveOptions, signal: () => AbortSignal | undefined, fn: (trace?: TraceIds) => Promise<T>): Promise<T>;
96
107
  /**
97
108
  * observeCommand 의 일반형 (자원 = `mext.resource <uri 템플릿>`, §12.3).
98
109
  * attributes 에 mext.transport · mext.principal.kind 를 더한다. 실패 · 취소 처리는 observeCommand 와 같다
99
110
  */
100
- export declare function observeSpan<T>(name: string, attributes: Record<string, string>, options: ObserveOptions, signal: () => AbortSignal | undefined, fn: () => Promise<T>): Promise<T>;
111
+ export declare function observeSpan<T>(name: string, attributes: Record<string, string>, options: ObserveOptions, signal: () => AbortSignal | undefined, fn: (trace?: TraceIds) => Promise<T>): Promise<T>;
101
112
  /**
102
113
  * 핸들러 예외 → onCommandError (await). MextError · CommandError · 취소 · MRTR 대기는 보고하지 않는다.
103
114
  * onCommandError 자체의 예외 = onerror (기본 stderr), 호출자에게 전파하지 않는다.
@@ -97,6 +97,7 @@ const TRACER_NAME = "@obov/mext";
97
97
  * 속성 = mext.command.id · mext.transport · mext.principal.kind. 인자 · 결과 · 토큰은 기록하지 않는다.
98
98
  * 실패: status ERROR + mext.error.code. 핸들러 예외만 recordException (MextError · CommandError 메시지는 입력 일부를 담을 수 있어 제외).
99
99
  * 취소 (signal abort · MRTR 대기) = status 없음.
100
+ * fn 인자 = span 의 traceId · spanId (span 없음 = undefined)
100
101
  */
101
102
  export function observeCommand(command, options, signal, fn) {
102
103
  return observeSpan(`mext.command ${command}`, { "mext.command.id": command }, options, signal, fn);
@@ -118,7 +119,7 @@ export function observeSpan(name, attributes, options, signal, fn) {
118
119
  const tracer = otel.trace.getTracer(TRACER_NAME);
119
120
  return tracer.startActiveSpan(name, { attributes: all }, parent, async (span) => {
120
121
  try {
121
- return await fn();
122
+ return await fn(traceIds(span));
122
123
  }
123
124
  catch (error) {
124
125
  if (!signal()?.aborted) {
@@ -139,6 +140,15 @@ export function observeSpan(name, attributes, options, signal, fn) {
139
140
  }
140
141
  });
141
142
  }
143
+ /** span 의 traceId · spanId. SDK 미등록 (non-recording span, 전부 0) · 형식 오류 = undefined */
144
+ function traceIds(span) {
145
+ const context = span.spanContext?.();
146
+ const traceId = context?.traceId;
147
+ if (typeof traceId !== "string" || traceId === "" || /^0+$/.test(traceId))
148
+ return undefined;
149
+ const spanId = context?.spanId;
150
+ return typeof spanId === "string" && spanId !== "" && !/^0+$/.test(spanId) ? { traceId, spanId } : { traceId };
151
+ }
142
152
  /**
143
153
  * 핸들러 예외 → onCommandError (await). MextError · CommandError · 취소 · MRTR 대기는 보고하지 않는다.
144
154
  * onCommandError 자체의 예외 = onerror (기본 stderr), 호출자에게 전파하지 않는다.
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../src/instrumentation.ts"],"names":[],"mappings":";;;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAG7C;;;;GAIG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAA;AA0DxD,oDAAoD;AACpD,MAAM,UAAU,mBAAmB,CAAC,GAAG,GAAW,OAAO,CAAC,GAAG,EAAE;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,oBAAoB,CAAC,CAAA;IACrD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5C,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,0BAA0B,CACxC,GAAG,GAAW,OAAO,CAAC,GAAG,EAAE,EAC3B,OAAO,GAA+B,EAAE;IAExC,OAAO,mBAAmB,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAA;AAC/D,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0C,CAAA;AAChE,MAAM,aAAa,GAAG,IAAI,OAAO,EAA0C,CAAA;AAE3E;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAA6C,EAC7C,OAAO,GAA+B,EAAE;IAExC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACpC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAwB,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC;QACxF,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;KACrE,CAAC,CAAA;IACF,OAAO;QACL,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzD,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,MAA4C;IAChE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,YAAY,GAAG,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;QACzG,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,MAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACpF,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,kCAAC,IAAI,EAAC,CAAC,IAAI,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;IACvF,CAAC;IACD,OAAO,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAA;AAC/E,CAAC;AAED,SAAS,IAAI,CACX,KAA8I,EAC9I,GAAM,EACN,IAA0C;IAE1C,IAAI,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACjB,MAAM,KAAK,CAAA;QACb,CAAC,CAAC,CAAA;QACF,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACzB,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,KAAc,EAAE,KAAa;IACnD,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAA;IACpD,KAAK,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,gBAAgB,CAAC,EAAE,CAAC;QACjD,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,UAAU,EAAE,CAAC;YAC7D,MAAM,IAAI,SAAS,CAAC,GAAG,KAAK,YAAY,GAAG,qBAAqB,CAAC,CAAA;QACnE,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,GAA4B,CAAA;IAC1C,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAA;IACxB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,0EAA0E;AAE1E,MAAM,QAAQ,GAAG,oBAAoB,CAAA;AACrC,IAAI,WAAqD,CAAA;AAEzD,SAAS,WAAW;IAClB,WAAW,KAAK,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,kCAAC,QAAQ,EAAC,CAAC,CAAA;IACnD,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,QAAgC;IACzD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,MAAM,QAAQ,EAAE,CAAsD,CAAA;QACnF,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAA;QACzC,OAAO,GAAG,EAAE,KAAK,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC,CAAE,GAAe,CAAC,CAAC,CAAC,SAAS,CAAA;IAC1G,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,kBAAkB;AAElB,MAAM,WAAW,GAAG,YAAY,CAAA;AAUhC;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,OAAuB,EACvB,MAAqC,EACrC,EAAoB;IAEpB,OAAO,WAAW,CAAC,gBAAgB,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;AACpG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,UAAkC,EAClC,OAAuB,EACvB,MAAqC,EACrC,EAAoB;IAEpB,MAAM,IAAI,GAAG,OAAO,CAAC,eAAe,EAAE,IAAI,CAAA;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,EAAE,CAAA;IACtB,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAA;IACnI,MAAM,GAAG,GAAG;QACV,GAAG,UAAU;QACb,gBAAgB,EAAE,OAAO,CAAC,SAAS,IAAI,OAAO;QAC9C,qBAAqB,EAAE,OAAO,CAAC,SAAS,CAAC,IAAI;KAC9C,CAAA;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAA;IAChD,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC9E,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAA;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC;gBACvB,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC3B,IAAI,CAAC,YAAY,CAAC,iBAAiB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;oBAChD,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;gBAC1E,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,YAAY,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,CAAA;oBACvD,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;oBAC3B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;gBACtH,CAAC;YACH,CAAC;YACD,MAAM,KAAK,CAAA;QACb,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,GAAG,EAAE,CAAA;QACZ,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAc,EACd,OAAe,EACf,OAAuB,EACvB,MAA+B,EAC/B,KAAK,GAA2C,EAAE;IAElD,MAAM,IAAI,GAAG,OAAO,CAAC,eAAe,EAAE,cAAc,CAAA;IACpD,IAAI,CAAC,IAAI,IAAI,eAAe,CAAC,KAAK,CAAC,IAAI,MAAM,EAAE,OAAO;QAAE,OAAM;IAC9D,MAAM,IAAI,GAAqB;QAC7B,OAAO;QACP,GAAG,KAAK;QACR,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,OAAO;QACvC,SAAS,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE;KACtE,CAAA;IACD,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC;IAAC,OAAO,SAAS,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,EAAE,OAAO,IAAI,cAAc,CAAA;QAClE,IAAI,CAAC;YACH,OAAO,CAAC,SAAS,CAAC,CAAA;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACvF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,OAAO,IAAI,CAAC,CAAA;AAClE,CAAC","sourcesContent":["import { existsSync } from \"node:fs\"\nimport { isAbsolute, join, resolve } from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\nimport { isDeclaredError } from \"./errors.ts\"\nimport type { Principal } from \"./principal.ts\"\n\n/**\n * instrumentation.ts (프로젝트 루트, 선택) · OpenTelemetry span (§11).\n * register() = 프로세스당 1회, 첫 명령어 실행 전. onCommandError = 핸들러 예외 보고.\n * span = @opentelemetry/api (선택 peer) 가 있을 때만. 없으면 no-op (실패한 import 1회 캐시).\n */\n\nexport const INSTRUMENTATION_FILE = \"instrumentation.ts\"\n\n/** 핸들러를 실행한 어댑터. 원격 CLI 는 핸들러를 실행하지 않으므로 없다 */\nexport type Transport = \"http\" | \"stdio\" | \"local\"\n\nexport interface CommandErrorInfo {\n /** 정식 명령어 ID. 자원 핸들러 (read · list · complete) 오류 = 자원 uri (템플릿 원문), 프롬프트 핸들러 오류 = 프롬프트 이름 */\n command: string\n /** 자원 핸들러 오류일 때 자원 uri (템플릿 원문, §12.3) */\n resource?: string\n /** 프롬프트 핸들러 (render · complete) 오류일 때 프롬프트 이름 (§12.3) */\n prompt?: string\n transport: Transport\n /** scopes · claims 는 넘기지 않는다 */\n principal: { id: string; kind: Principal[\"kind\"] }\n}\n\n/** instrumentation.ts 의 export */\nexport interface InstrumentationModule {\n register?: () => unknown\n onCommandError?: (error: unknown, info: CommandErrorInfo) => unknown\n}\n\n/** 어댑터가 InvokeOptions.instrumentation 으로 넘기는 로드 결과 */\nexport interface Instrumentation {\n /** 핸들러 예외 (MextError 외, 취소 · MRTR 대기 제외). 자체 예외는 stderr 로 보고하고 응답은 바꾸지 않는다 */\n onCommandError?: (error: unknown, info: CommandErrorInfo) => unknown\n /** @opentelemetry/api. undefined = span 없음 */\n otel?: OtelApi\n /** onCommandError 의 예외 보고. 기본 stderr */\n onerror?: (error: unknown) => void\n}\n\nexport interface LoadInstrumentationOptions {\n /** @opentelemetry/api 로더 (테스트 주입). 기본 = 동적 import, 결과는 프로세스당 캐시 */\n importOtel?: () => Promise<unknown>\n onerror?: (error: unknown) => void\n}\n\n/** mext 가 쓰는 @opentelemetry/api 의 부분 (타입 의존 없이) */\nexport interface OtelApi {\n trace: { getTracer(name: string, version?: string): OtelTracer }\n context: { active(): unknown }\n propagation: { extract(context: unknown, carrier: unknown): unknown }\n SpanStatusCode: { ERROR: number }\n}\n\ninterface OtelTracer {\n startActiveSpan<T>(name: string, options: { attributes: Record<string, string> }, context: unknown, fn: (span: OtelSpan) => T): T\n}\n\ninterface OtelSpan {\n setAttribute(key: string, value: string): unknown\n setStatus(status: { code: number; message?: string }): unknown\n recordException(exception: unknown): unknown\n end(): void\n}\n\n/** cwd 의 instrumentation.ts 절대 경로. 없으면 undefined */\nexport function findInstrumentation(cwd: string = process.cwd()): string | undefined {\n const file = join(resolve(cwd), INSTRUMENTATION_FILE)\n return existsSync(file) ? file : undefined\n}\n\n/** findInstrumentation(cwd) → loadInstrumentation. mext dev · mext run (로컬) · stdio 용 */\nexport function loadProjectInstrumentation(\n cwd: string = process.cwd(),\n options: LoadInstrumentationOptions = {},\n): Promise<Instrumentation> {\n return loadInstrumentation(findInstrumentation(cwd), options)\n}\n\nconst loaded = new Map<string, Promise<InstrumentationModule>>()\nconst loadedModules = new WeakMap<object, Promise<InstrumentationModule>>()\n\n/**\n * instrumentation 모듈 (파일 경로 · URL · import 된 모듈) 을 프로세스당 1회 import 하고 register() 를 await.\n * 같은 모듈의 두 번째 호출 = register 재호출 없이 같은 hooks. register 실패 = reject (다음 호출에서 재시도).\n * source 없음 = hooks 없이 span 만. 빌드 산출물 (.mext/server/instrumentation.js) 도 같은 함수.\n */\nexport async function loadInstrumentation(\n source?: string | URL | InstrumentationModule,\n options: LoadInstrumentationOptions = {},\n): Promise<Instrumentation> {\n const [mod, otel] = await Promise.all([\n source === undefined ? Promise.resolve<InstrumentationModule>({}) : registerOnce(source),\n options.importOtel ? resolveOtel(options.importOtel) : defaultOtel(),\n ])\n return {\n ...(mod.onCommandError ? { onCommandError: mod.onCommandError } : {}),\n ...(otel ? { otel } : {}),\n ...(options.onerror ? { onerror: options.onerror } : {}),\n }\n}\n\nfunction registerOnce(source: string | URL | InstrumentationModule): Promise<InstrumentationModule> {\n if (typeof source === \"string\" || source instanceof URL) {\n const isPath = typeof source === \"string\" && (isAbsolute(source) || !/^[a-z][a-z0-9+.-]+:/i.test(source))\n const href = isPath ? pathToFileURL(resolve(source as string)).href : String(source)\n return memo(loaded, href, () => import(href).then((m: unknown) => register(m, href)))\n }\n return memo(loadedModules, source, () => register(source, \"instrumentation\"))\n}\n\nfunction memo<K>(\n cache: { get(k: K): Promise<InstrumentationModule> | undefined; set(k: K, v: Promise<InstrumentationModule>): unknown; delete(k: K): unknown },\n key: K,\n load: () => Promise<InstrumentationModule>,\n): Promise<InstrumentationModule> {\n let pending = cache.get(key)\n if (!pending) {\n pending = load().catch((error: unknown) => {\n cache.delete(key)\n throw error\n })\n cache.set(key, pending)\n }\n return pending\n}\n\nasync function register(value: unknown, label: string): Promise<InstrumentationModule> {\n const mod = (value ?? {}) as Record<string, unknown>\n for (const key of [\"register\", \"onCommandError\"]) {\n if (mod[key] !== undefined && typeof mod[key] !== \"function\") {\n throw new TypeError(`${label}: export ${key} must be a function`)\n }\n }\n const hooks = mod as InstrumentationModule\n await hooks.register?.()\n return hooks\n}\n\n/* @opentelemetry/api (선택 peer). 번들러가 해석 · 포함하지 않도록 specifier 는 변수 (R5) */\n\nconst OTEL_API = \"@opentelemetry/api\"\nlet otelDefault: Promise<OtelApi | undefined> | undefined\n\nfunction defaultOtel(): Promise<OtelApi | undefined> {\n otelDefault ??= resolveOtel(() => import(OTEL_API))\n return otelDefault\n}\n\nasync function resolveOtel(importer: () => Promise<unknown>): Promise<OtelApi | undefined> {\n try {\n const mod = (await importer()) as Partial<OtelApi> & { default?: Partial<OtelApi> }\n const api = mod.trace ? mod : mod.default\n return api?.trace && api.context && api.propagation && api.SpanStatusCode ? (api as OtelApi) : undefined\n } catch {\n return undefined\n }\n}\n\n/* invoke 에서 사용 */\n\nconst TRACER_NAME = \"@obov/mext\"\n\nexport interface ObserveOptions {\n instrumentation?: Instrumentation\n transport?: Transport\n principal: Principal\n /** W3C trace context carrier (MCP params._meta 의 traceparent · tracestate) */\n traceContext?: Record<string, unknown>\n}\n\n/**\n * 명령어 실행 1회를 `mext.command <id>` span 으로 감싼다 (otel 있을 때만, 없으면 fn 그대로).\n * 속성 = mext.command.id · mext.transport · mext.principal.kind. 인자 · 결과 · 토큰은 기록하지 않는다.\n * 실패: status ERROR + mext.error.code. 핸들러 예외만 recordException (MextError · CommandError 메시지는 입력 일부를 담을 수 있어 제외).\n * 취소 (signal abort · MRTR 대기) = status 없음.\n */\nexport function observeCommand<T>(\n command: string,\n options: ObserveOptions,\n signal: () => AbortSignal | undefined,\n fn: () => Promise<T>,\n): Promise<T> {\n return observeSpan(`mext.command ${command}`, { \"mext.command.id\": command }, options, signal, fn)\n}\n\n/**\n * observeCommand 의 일반형 (자원 = `mext.resource <uri 템플릿>`, §12.3).\n * attributes 에 mext.transport · mext.principal.kind 를 더한다. 실패 · 취소 처리는 observeCommand 와 같다\n */\nexport function observeSpan<T>(\n name: string,\n attributes: Record<string, string>,\n options: ObserveOptions,\n signal: () => AbortSignal | undefined,\n fn: () => Promise<T>,\n): Promise<T> {\n const otel = options.instrumentation?.otel\n if (!otel) return fn()\n const parent = options.traceContext ? otel.propagation.extract(otel.context.active(), options.traceContext) : otel.context.active()\n const all = {\n ...attributes,\n \"mext.transport\": options.transport ?? \"local\",\n \"mext.principal.kind\": options.principal.kind,\n }\n const tracer = otel.trace.getTracer(TRACER_NAME)\n return tracer.startActiveSpan(name, { attributes: all }, parent, async (span) => {\n try {\n return await fn()\n } catch (error) {\n if (!signal()?.aborted) {\n if (isDeclaredError(error)) {\n span.setAttribute(\"mext.error.code\", error.code)\n span.setStatus({ code: otel.SpanStatusCode.ERROR, message: error.code })\n } else {\n span.setAttribute(\"mext.error.code\", \"EXECUTION_ERROR\")\n span.recordException(error)\n span.setStatus({ code: otel.SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) })\n }\n }\n throw error\n } finally {\n span.end()\n }\n })\n}\n\n/**\n * 핸들러 예외 → onCommandError (await). MextError · CommandError · 취소 · MRTR 대기는 보고하지 않는다.\n * onCommandError 자체의 예외 = onerror (기본 stderr), 호출자에게 전파하지 않는다.\n */\nexport async function reportCommandError(\n error: unknown,\n command: string,\n options: ObserveOptions,\n signal: AbortSignal | undefined,\n extra: { resource?: string; prompt?: string } = {},\n): Promise<void> {\n const hook = options.instrumentation?.onCommandError\n if (!hook || isDeclaredError(error) || signal?.aborted) return\n const info: CommandErrorInfo = {\n command,\n ...extra,\n transport: options.transport ?? \"local\",\n principal: { id: options.principal.id, kind: options.principal.kind },\n }\n try {\n await hook(error, info)\n } catch (hookError) {\n const onerror = options.instrumentation?.onerror ?? defaultOnError\n try {\n onerror(hookError)\n } catch {\n // 보고는 응답을 바꾸지 않는다\n }\n }\n}\n\nfunction defaultOnError(error: unknown): void {\n const message = error instanceof Error ? (error.stack ?? error.message) : String(error)\n process.stderr.write(`mext: onCommandError threw: ${message}\\n`)\n}\n"]}
1
+ {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../src/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\"\nimport { isAbsolute, join, resolve } from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\nimport { isDeclaredError } from \"./errors.ts\"\nimport type { Principal } from \"./principal.ts\"\n\n/**\n * instrumentation.ts (프로젝트 루트, 선택) · OpenTelemetry span (§11).\n * register() = 프로세스당 1회, 첫 명령어 실행 전. onCommandError = 핸들러 예외 보고.\n * span = @opentelemetry/api (선택 peer) 가 있을 때만. 없으면 no-op (실패한 import 1회 캐시).\n */\n\nexport const INSTRUMENTATION_FILE = \"instrumentation.ts\"\n\n/** 핸들러를 실행한 어댑터. 원격 CLI 는 핸들러를 실행하지 않으므로 없다 */\nexport type Transport = \"http\" | \"stdio\" | \"local\"\n\nexport interface CommandErrorInfo {\n /** 정식 명령어 ID. 자원 핸들러 (read · list · complete) 오류 = 자원 uri (템플릿 원문), 프롬프트 핸들러 오류 = 프롬프트 이름 */\n command: string\n /** 자원 핸들러 오류일 때 자원 uri (템플릿 원문, §12.3) */\n resource?: string\n /** 프롬프트 핸들러 (render · complete) 오류일 때 프롬프트 이름 (§12.3) */\n prompt?: string\n transport: Transport\n /** scopes · claims 는 넘기지 않는다 */\n principal: { id: string; kind: Principal[\"kind\"] }\n}\n\n/** instrumentation.ts 의 export */\nexport interface InstrumentationModule {\n register?: () => unknown\n onCommandError?: (error: unknown, info: CommandErrorInfo) => unknown\n}\n\n/** 어댑터가 InvokeOptions.instrumentation 으로 넘기는 로드 결과 */\nexport interface Instrumentation {\n /** 핸들러 예외 (MextError 외, 취소 · MRTR 대기 제외). 자체 예외는 stderr 로 보고하고 응답은 바꾸지 않는다 */\n onCommandError?: (error: unknown, info: CommandErrorInfo) => unknown\n /** @opentelemetry/api. undefined = span 없음 */\n otel?: OtelApi\n /** onCommandError 의 예외 보고. 기본 stderr */\n onerror?: (error: unknown) => void\n}\n\nexport interface LoadInstrumentationOptions {\n /** @opentelemetry/api 로더 (테스트 주입). 기본 = 동적 import, 결과는 프로세스당 캐시 */\n importOtel?: () => Promise<unknown>\n onerror?: (error: unknown) => void\n}\n\n/** mext 가 쓰는 @opentelemetry/api 의 부분 (타입 의존 없이) */\nexport interface OtelApi {\n trace: { getTracer(name: string, version?: string): OtelTracer }\n context: { active(): unknown }\n propagation: { extract(context: unknown, carrier: unknown): unknown }\n SpanStatusCode: { ERROR: number }\n}\n\ninterface OtelTracer {\n startActiveSpan<T>(name: string, options: { attributes: Record<string, string> }, context: unknown, fn: (span: OtelSpan) => T): T\n}\n\ninterface OtelSpan {\n /** 없는 구현 (테스트 대역) 도 허용 */\n spanContext?(): { traceId?: unknown; spanId?: unknown } | undefined\n setAttribute(key: string, value: string): unknown\n setStatus(status: { code: number; message?: string }): unknown\n recordException(exception: unknown): unknown\n end(): void\n}\n\n/** cwd 의 instrumentation.ts 절대 경로. 없으면 undefined */\nexport function findInstrumentation(cwd: string = process.cwd()): string | undefined {\n const file = join(resolve(cwd), INSTRUMENTATION_FILE)\n return existsSync(file) ? file : undefined\n}\n\n/** findInstrumentation(cwd) → loadInstrumentation. mext dev · mext run (로컬) · stdio 용 */\nexport function loadProjectInstrumentation(\n cwd: string = process.cwd(),\n options: LoadInstrumentationOptions = {},\n): Promise<Instrumentation> {\n return loadInstrumentation(findInstrumentation(cwd), options)\n}\n\nconst loaded = new Map<string, Promise<InstrumentationModule>>()\nconst loadedModules = new WeakMap<object, Promise<InstrumentationModule>>()\n\n/**\n * instrumentation 모듈 (파일 경로 · URL · import 된 모듈) 을 프로세스당 1회 import 하고 register() 를 await.\n * 같은 모듈의 두 번째 호출 = register 재호출 없이 같은 hooks. register 실패 = reject (다음 호출에서 재시도).\n * source 없음 = hooks 없이 span 만. 빌드 산출물 (.mext/server/instrumentation.js) 도 같은 함수.\n */\nexport async function loadInstrumentation(\n source?: string | URL | InstrumentationModule,\n options: LoadInstrumentationOptions = {},\n): Promise<Instrumentation> {\n const [mod, otel] = await Promise.all([\n source === undefined ? Promise.resolve<InstrumentationModule>({}) : registerOnce(source),\n options.importOtel ? resolveOtel(options.importOtel) : defaultOtel(),\n ])\n return {\n ...(mod.onCommandError ? { onCommandError: mod.onCommandError } : {}),\n ...(otel ? { otel } : {}),\n ...(options.onerror ? { onerror: options.onerror } : {}),\n }\n}\n\nfunction registerOnce(source: string | URL | InstrumentationModule): Promise<InstrumentationModule> {\n if (typeof source === \"string\" || source instanceof URL) {\n const isPath = typeof source === \"string\" && (isAbsolute(source) || !/^[a-z][a-z0-9+.-]+:/i.test(source))\n const href = isPath ? pathToFileURL(resolve(source as string)).href : String(source)\n return memo(loaded, href, () => import(href).then((m: unknown) => register(m, href)))\n }\n return memo(loadedModules, source, () => register(source, \"instrumentation\"))\n}\n\nfunction memo<K>(\n cache: { get(k: K): Promise<InstrumentationModule> | undefined; set(k: K, v: Promise<InstrumentationModule>): unknown; delete(k: K): unknown },\n key: K,\n load: () => Promise<InstrumentationModule>,\n): Promise<InstrumentationModule> {\n let pending = cache.get(key)\n if (!pending) {\n pending = load().catch((error: unknown) => {\n cache.delete(key)\n throw error\n })\n cache.set(key, pending)\n }\n return pending\n}\n\nasync function register(value: unknown, label: string): Promise<InstrumentationModule> {\n const mod = (value ?? {}) as Record<string, unknown>\n for (const key of [\"register\", \"onCommandError\"]) {\n if (mod[key] !== undefined && typeof mod[key] !== \"function\") {\n throw new TypeError(`${label}: export ${key} must be a function`)\n }\n }\n const hooks = mod as InstrumentationModule\n await hooks.register?.()\n return hooks\n}\n\n/* @opentelemetry/api (선택 peer). 번들러가 해석 · 포함하지 않도록 specifier 는 변수 (R5) */\n\nconst OTEL_API = \"@opentelemetry/api\"\nlet otelDefault: Promise<OtelApi | undefined> | undefined\n\nfunction defaultOtel(): Promise<OtelApi | undefined> {\n otelDefault ??= resolveOtel(() => import(OTEL_API))\n return otelDefault\n}\n\nasync function resolveOtel(importer: () => Promise<unknown>): Promise<OtelApi | undefined> {\n try {\n const mod = (await importer()) as Partial<OtelApi> & { default?: Partial<OtelApi> }\n const api = mod.trace ? mod : mod.default\n return api?.trace && api.context && api.propagation && api.SpanStatusCode ? (api as OtelApi) : undefined\n } catch {\n return undefined\n }\n}\n\n/* invoke 에서 사용 */\n\nconst TRACER_NAME = \"@obov/mext\"\n\n/** span 식별자 (감사 이벤트의 traceId · spanId, §11.1). 유효하지 않은 (전부 0) 값은 넘기지 않는다 */\nexport interface TraceIds {\n traceId: string\n spanId?: string\n}\n\nexport interface ObserveOptions {\n instrumentation?: Instrumentation\n transport?: Transport\n principal: Principal\n /** W3C trace context carrier (MCP params._meta 의 traceparent · tracestate) */\n traceContext?: Record<string, unknown>\n}\n\n/**\n * 명령어 실행 1회를 `mext.command <id>` span 으로 감싼다 (otel 있을 때만, 없으면 fn 그대로).\n * 속성 = mext.command.id · mext.transport · mext.principal.kind. 인자 · 결과 · 토큰은 기록하지 않는다.\n * 실패: status ERROR + mext.error.code. 핸들러 예외만 recordException (MextError · CommandError 메시지는 입력 일부를 담을 수 있어 제외).\n * 취소 (signal abort · MRTR 대기) = status 없음.\n * fn 인자 = span 의 traceId · spanId (span 없음 = undefined)\n */\nexport function observeCommand<T>(\n command: string,\n options: ObserveOptions,\n signal: () => AbortSignal | undefined,\n fn: (trace?: TraceIds) => Promise<T>,\n): Promise<T> {\n return observeSpan(`mext.command ${command}`, { \"mext.command.id\": command }, options, signal, fn)\n}\n\n/**\n * observeCommand 의 일반형 (자원 = `mext.resource <uri 템플릿>`, §12.3).\n * attributes 에 mext.transport · mext.principal.kind 를 더한다. 실패 · 취소 처리는 observeCommand 와 같다\n */\nexport function observeSpan<T>(\n name: string,\n attributes: Record<string, string>,\n options: ObserveOptions,\n signal: () => AbortSignal | undefined,\n fn: (trace?: TraceIds) => Promise<T>,\n): Promise<T> {\n const otel = options.instrumentation?.otel\n if (!otel) return fn()\n const parent = options.traceContext ? otel.propagation.extract(otel.context.active(), options.traceContext) : otel.context.active()\n const all = {\n ...attributes,\n \"mext.transport\": options.transport ?? \"local\",\n \"mext.principal.kind\": options.principal.kind,\n }\n const tracer = otel.trace.getTracer(TRACER_NAME)\n return tracer.startActiveSpan(name, { attributes: all }, parent, async (span) => {\n try {\n return await fn(traceIds(span))\n } catch (error) {\n if (!signal()?.aborted) {\n if (isDeclaredError(error)) {\n span.setAttribute(\"mext.error.code\", error.code)\n span.setStatus({ code: otel.SpanStatusCode.ERROR, message: error.code })\n } else {\n span.setAttribute(\"mext.error.code\", \"EXECUTION_ERROR\")\n span.recordException(error)\n span.setStatus({ code: otel.SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) })\n }\n }\n throw error\n } finally {\n span.end()\n }\n })\n}\n\n/** span 의 traceId · spanId. SDK 미등록 (non-recording span, 전부 0) · 형식 오류 = undefined */\nfunction traceIds(span: OtelSpan): TraceIds | undefined {\n const context = span.spanContext?.()\n const traceId = context?.traceId\n if (typeof traceId !== \"string\" || traceId === \"\" || /^0+$/.test(traceId)) return undefined\n const spanId = context?.spanId\n return typeof spanId === \"string\" && spanId !== \"\" && !/^0+$/.test(spanId) ? { traceId, spanId } : { traceId }\n}\n\n/**\n * 핸들러 예외 → onCommandError (await). MextError · CommandError · 취소 · MRTR 대기는 보고하지 않는다.\n * onCommandError 자체의 예외 = onerror (기본 stderr), 호출자에게 전파하지 않는다.\n */\nexport async function reportCommandError(\n error: unknown,\n command: string,\n options: ObserveOptions,\n signal: AbortSignal | undefined,\n extra: { resource?: string; prompt?: string } = {},\n): Promise<void> {\n const hook = options.instrumentation?.onCommandError\n if (!hook || isDeclaredError(error) || signal?.aborted) return\n const info: CommandErrorInfo = {\n command,\n ...extra,\n transport: options.transport ?? \"local\",\n principal: { id: options.principal.id, kind: options.principal.kind },\n }\n try {\n await hook(error, info)\n } catch (hookError) {\n const onerror = options.instrumentation?.onerror ?? defaultOnError\n try {\n onerror(hookError)\n } catch {\n // 보고는 응답을 바꾸지 않는다\n }\n }\n}\n\nfunction defaultOnError(error: unknown): void {\n const message = error instanceof Error ? (error.stack ?? error.message) : String(error)\n process.stderr.write(`mext: onCommandError threw: ${message}\\n`)\n}\n"]}
package/dist/invoke.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import * as z from "zod";
2
+ import { type ApprovalConfig } from "./approval.ts";
3
+ import { type ContentBlock } from "./content.ts";
4
+ import { type AuditConfig } from "./audit.ts";
5
+ import { type ReportedClient } from "./caller.ts";
2
6
  import type { CommandContext, InputSchema, MextApp, ProgressUpdate } from "./command.ts";
3
7
  import { MextError } from "./errors.ts";
4
8
  import { type Instrumentation, type Transport } from "./instrumentation.ts";
@@ -34,31 +38,55 @@ export interface InvokeOptions {
34
38
  app?: MextApp;
35
39
  /** readOnly 명령어만 허용 (split 의 query 도구). 그 외 = PERMISSION_DENIED */
36
40
  readOnly?: boolean;
41
+ /**
42
+ * 응답을 기다리게 하지 않을 작업 (숨긴 명령어 호출 시도의 감사 전달, §11.1) 을 넘겨받는다. MCP 어댑터 = waitUntil 또는 그대로 둠.
43
+ * 없음 = invoke 가 끝까지 기다린다 (로컬 실행: 프로세스가 곧 끝난다)
44
+ */
45
+ background?: (work: Promise<void>) => void;
37
46
  /** loadInstrumentation 결과. onCommandError · mext.command span. 기본 = 없음 */
38
47
  instrumentation?: Instrumentation;
39
- /** 실행 어댑터 (onCommandError · span 속성). 기본 "local" */
48
+ /** 실행 어댑터 (onCommandError · span 속성 · ctx.caller.transport · 기본 표면). 기본 "local" */
40
49
  transport?: Transport;
50
+ /** 클라이언트가 신고한 clientInfo (ctx.caller.client). 자가 신고 값. 기본 = 없음 */
51
+ client?: ReportedClient;
41
52
  /** W3C trace context carrier (MCP params._meta 의 traceparent · tracestate). span 의 부모 */
42
53
  traceContext?: Record<string, unknown>;
43
54
  /** ctx.resourceUpdated (§12.5). 기본 = no-op */
44
55
  resourceUpdated?: (uri: string) => Promise<void>;
45
56
  /**
46
- * tasks 확장 (§12.6). 입력 검증 · 권한 · requiresConfirmation 통과 후 execute 직전에 호출.
57
+ * tasks 확장 (§12.6). 입력 검증 · 권한 · 승인 (§4) 통과 후 execute 직전에 호출.
47
58
  * 반환 = 분리 실행: invoke 는 handle 로 바로 resolve, execute 는 반환된 signal · ask · progress 로 계속 실행 후 settle.
48
59
  * undefined = 동기 실행 (지금과 같음). span · onCommandError 는 분리 실행 전체를 덮는다
49
60
  */
50
61
  detach?: (entry: RegisteredCommand) => Promise<DetachedExecution | undefined>;
62
+ /** 감사 (config.audit, §11.1). 없음 = 이벤트 없음 */
63
+ audit?: AuditConfig;
64
+ /** 승인의 표면별 확인 방식 (config.approval, §4 승인). 없음 = 모든 표면 ask */
65
+ approval?: ApprovalConfig;
66
+ /**
67
+ * 요청에 확인 표시가 실려 있음 (MCP params._meta["dev.mext/approval"] = { confirmed: true }). 클라이언트가 실은 값이다:
68
+ * client 방식 표면 + 서버가 정한 행위자 human 일 때만 인정한다. 기본 false
69
+ */
70
+ confirmed?: boolean;
71
+ /**
72
+ * 이 호출이 입력 대기로 끝났는지 (MRTR 라운드: 답 없는 ctx.ask 가 있어 결과를 버리고 InputRequiredResult 를 돌려준다).
73
+ * true = 감사 이벤트 없음 (답을 받아 끝난 라운드가 1건)
74
+ */
75
+ pendingInput?: () => boolean;
51
76
  }
52
77
  /** detach 의 반환. execute 의 ctx 를 요청과 분리한다 (R2: 응답 종료 ≠ 취소) */
53
78
  export interface DetachedExecution {
54
79
  /** invoke 결과의 detached (MCP = CreateTaskResult) */
55
80
  handle: unknown;
81
+ /** 감사 이벤트의 taskId (§11.1) */
82
+ taskId?: string;
56
83
  signal: AbortSignal;
57
84
  ask: AskHandler;
58
85
  progress: (update: ProgressUpdate) => void;
59
86
  /** execute 종료. 값 = output 검증 후, 예외 = onCommandError 보고 후. 거부하지 않는다 */
60
87
  settle(outcome: {
61
88
  value: unknown;
89
+ content?: ContentBlock[];
62
90
  } | {
63
91
  error: unknown;
64
92
  }): Promise<void>;
@@ -67,14 +95,17 @@ export interface InvokeResult {
67
95
  /** 정식 ID (별칭으로 호출해도) */
68
96
  command: string;
69
97
  value: unknown;
98
+ /** 결과 콘텐츠 (image · audio · resource_link, spec §4). 없으면 생략 */
99
+ content?: ContentBlock[];
70
100
  /** detach 로 분리 실행됨: DetachedExecution.handle (value 는 undefined) */
71
101
  detached?: unknown;
72
102
  }
73
103
  /**
74
- * 어댑터 공통 실행기: ID 해석 (별칭 포함) → 권한 → (lazy 면 모듈 import) → Zod safeParse 1회 → 확인 (requiresConfirmation) → execute.
75
- * 실패는 MextError (UNKNOWN_COMMAND · PERMISSION_DENIED · INVALID_ARGUMENT · INPUT_UNAVAILABLE · INPUT_DECLINED).
104
+ * 어댑터 공통 실행기: ID 해석 (별칭 포함. 주체의 표면에서 숨긴 명령어 = 없는 ID, §4 expose) → 권한 → (lazy 면 모듈 import) → Zod safeParse 1회 → 가드 (authorize, §4) → 승인 (needsApproval · requiresConfirmation, §4) → execute.
105
+ * 실패는 MextError (UNKNOWN_COMMAND · PERMISSION_DENIED · INVALID_ARGUMENT · APPROVAL_REQUIRED · INPUT_UNAVAILABLE · INPUT_DECLINED).
76
106
  * 핸들러 예외는 그대로 전파 (instrumentation 있으면 onCommandError 보고 후).
77
107
  * instrumentation.otel 있으면 findCommand 뒤 전체를 `mext.command <id>` span 으로 감싼다.
108
+ * options.audit 있으면 호출 1건 = 감사 이벤트 1건 (§11.1. UNKNOWN_COMMAND · 입력 검증 실패 · 입력 대기 라운드 제외).
78
109
  */
79
110
  export declare function invoke(registry: Registry, id: string, rawInput: unknown, options: InvokeOptions): Promise<InvokeResult>;
80
111
  /** ID · 별칭 → 명령어. 없으면 UNKNOWN_COMMAND (suggested · usage). 구현 = lookup.ts (help · 고객 CLI 공용) */
@@ -96,7 +127,7 @@ export declare class OutputValidationError extends Error {
96
127
  export declare function createContext(entry: RegisteredCommand, options: InvokeOptions): CommandContext;
97
128
  /** ctx.resourceUpdated uri: 비어 있지 않은 문자열. 아니면 TypeError (핸들러 버그) */
98
129
  export declare function assertResourceUri(uri: unknown): asserts uri is string;
99
- /** requiresConfirmation 확인 문구. 검증된 입력을 포함 (무엇을 승인하는지) */
130
+ /** 승인 확인 문구 (ask 방식). 검증된 입력을 포함 (무엇을 승인하는지) */
100
131
  export declare function confirmationMessage(entry: RegisteredCommand, input: unknown): string;
101
132
  /** INVALID_ARGUMENT (missing_fields · issues · expected_schema · examples). 프롬프트 인자 (§12.3) 도 사용 */
102
133
  export declare function invalidArgument(command: string, raw: unknown, zodIssues: readonly z.core.$ZodIssue[], expectedSchema: JsonSchema, examples: unknown[]): MextError<"INVALID_ARGUMENT">;
package/dist/invoke.js CHANGED
@@ -1,60 +1,114 @@
1
1
  import * as z from "zod";
2
- import { inputDeclined, MextError } from "./errors.js";
2
+ import { approvalModeOf } from "./approval.js";
3
+ import { splitResult } from "./content.js";
4
+ import { auditHidden, startAudit } from "./audit.js";
5
+ import { callerOf, surfaceOf } from "./caller.js";
6
+ import { approvalRequired, inputDeclined, isCommandError, isMextError, MextError } from "./errors.js";
7
+ import { exposedRegistry } from "./expose.js";
3
8
  import { observeCommand, reportCommandError } from "./instrumentation.js";
4
9
  import { findCommand } from "./lookup.js";
5
10
  import { hasScope } from "./principal.js";
6
- import { loadCommand } from "./registry.js";
11
+ import { loadCommand, resolveCommand } from "./registry.js";
7
12
  /**
8
- * 어댑터 공통 실행기: ID 해석 (별칭 포함) → 권한 → (lazy 면 모듈 import) → Zod safeParse 1회 → 확인 (requiresConfirmation) → execute.
9
- * 실패는 MextError (UNKNOWN_COMMAND · PERMISSION_DENIED · INVALID_ARGUMENT · INPUT_UNAVAILABLE · INPUT_DECLINED).
13
+ * 어댑터 공통 실행기: ID 해석 (별칭 포함. 주체의 표면에서 숨긴 명령어 = 없는 ID, §4 expose) → 권한 → (lazy 면 모듈 import) → Zod safeParse 1회 → 가드 (authorize, §4) → 승인 (needsApproval · requiresConfirmation, §4) → execute.
14
+ * 실패는 MextError (UNKNOWN_COMMAND · PERMISSION_DENIED · INVALID_ARGUMENT · APPROVAL_REQUIRED · INPUT_UNAVAILABLE · INPUT_DECLINED).
10
15
  * 핸들러 예외는 그대로 전파 (instrumentation 있으면 onCommandError 보고 후).
11
16
  * instrumentation.otel 있으면 findCommand 뒤 전체를 `mext.command <id>` span 으로 감싼다.
17
+ * options.audit 있으면 호출 1건 = 감사 이벤트 1건 (§11.1. UNKNOWN_COMMAND · 입력 검증 실패 · 입력 대기 라운드 제외).
12
18
  */
13
19
  export async function invoke(registry, id, rawInput, options) {
14
- const entry = findCommand(registry, id);
20
+ // expose (§4): 주체의 표면에서 보이는 명령어만. 숨긴 명령어 = UNKNOWN_COMMAND (없는 ID 와 같은 본문)
21
+ const view = exposedRegistry(registry, surfaceOf(options.principal, options.transport ?? "local"));
22
+ let entry;
23
+ try {
24
+ entry = findCommand(view, id);
25
+ }
26
+ catch (error) {
27
+ // 숨긴 명령어의 호출 시도 기록 (audit.includeHidden, §11.1). 응답 = 이 오류 그대로. 꺼져 있으면 찾지 않는다
28
+ if (options.audit?.includeHidden === true && view !== registry) {
29
+ const hidden = resolveCommand(registry, id);
30
+ const work = hidden && auditHidden(hidden, options);
31
+ if (work) {
32
+ if (options.background)
33
+ options.background(work);
34
+ else
35
+ await work;
36
+ }
37
+ }
38
+ throw error;
39
+ }
15
40
  // span 의 취소 판정 signal. 분리 실행 후 = task signal
16
41
  let signal = options.signal;
17
42
  let handoff;
18
43
  const handedOff = options.detach ? new Promise((resolve) => (handoff = resolve)) : undefined;
19
- const run = observeCommand(entry.id, options, () => signal, async () => {
44
+ // 감사 (§11.1). sink 없음 · 대상 아님 = undefined
45
+ const trail = startAudit(entry, options);
46
+ const body = async () => {
20
47
  authorize(entry, options.principal, { readOnly: options.readOnly });
21
48
  // lazy Registry (mext start) = 여기서 명령어 모듈 import. 권한 · ID 오류는 import 없이
22
49
  const command = await loadCommand(entry);
23
- const input = await parseInput(entry, rawInput);
50
+ let input;
51
+ try {
52
+ input = await parseInput(entry, rawInput);
53
+ }
54
+ catch (error) {
55
+ // 입력 검증 실패 = 감사 이벤트 없음
56
+ trail?.skip();
57
+ throw error;
58
+ }
59
+ trail?.validated(command, input);
24
60
  const ctx = createContext(entry, options);
25
61
  ctx.signal.throwIfAborted();
26
- if (entry.command.requiresConfirmation) {
27
- // 확인은 ctx.ask 경유 (CLI = 프롬프트 · --yes, MCP = MRTR). ask 미주입 = INPUT_UNAVAILABLE
28
- const confirmed = await ctx.ask({ kind: "confirm", message: confirmationMessage(entry, input) });
29
- if (!confirmed)
30
- throw inputDeclined(entry.id, "confirm");
31
- ctx.signal.throwIfAborted();
32
- }
62
+ // 가드 = 로드한 명령어의 authorize (lazy Registry 의 메타데이터 명령어에는 함수가 없다)
63
+ await guard(entry, command, input, ctx, options);
64
+ await approve(entry, command, input, ctx, options, trail);
33
65
  const detached = await options.detach?.(entry);
34
66
  if (detached) {
35
67
  signal = detached.signal;
36
68
  handoff({ command: entry.id, value: undefined, detached: detached.handle });
37
69
  const taskCtx = createContext(entry, { ...options, signal: detached.signal, ask: detached.ask, progress: detached.progress });
70
+ trail?.task(detached.taskId);
71
+ // 감사 = 분리 실행의 실제 종료 시점, task 결과 기록 (settle) 전
38
72
  try {
39
- const value = await parseOutput(entry.id, command, await command.execute(input, taskCtx));
40
- await detached.settle({ value });
41
- return { command: entry.id, value };
73
+ const { value, content } = await settleOutput(entry.id, command, await command.execute(input, taskCtx));
74
+ await trail?.finish({ value }, false);
75
+ await detached.settle(content ? { value, content } : { value });
76
+ return { command: entry.id, value, ...(content ? { content } : {}) };
42
77
  }
43
78
  catch (error) {
44
79
  await reportCommandError(error, entry.id, options, taskCtx.signal);
80
+ await trail?.finish({ error }, taskCtx.signal.aborted);
45
81
  await detached.settle({ error });
46
82
  throw error;
47
83
  }
48
84
  }
49
85
  try {
50
- const value = await parseOutput(entry.id, command, await command.execute(input, ctx));
51
- return { command: entry.id, value };
86
+ const { value, content } = await settleOutput(entry.id, command, await command.execute(input, ctx));
87
+ return { command: entry.id, value, ...(content ? { content } : {}) };
52
88
  }
53
89
  catch (error) {
54
90
  await reportCommandError(error, entry.id, options, ctx.signal);
55
91
  throw error;
56
92
  }
57
- });
93
+ };
94
+ const run = observeCommand(entry.id, options, () => signal, !trail
95
+ ? body
96
+ : async (trace) => {
97
+ trail.trace(trace);
98
+ try {
99
+ const result = await body();
100
+ if (options.pendingInput?.())
101
+ trail.skip();
102
+ await trail.finish({ value: result.value }, false);
103
+ return result;
104
+ }
105
+ catch (error) {
106
+ if (options.pendingInput?.())
107
+ trail.skip();
108
+ await trail.finish({ error }, signal?.aborted === true);
109
+ throw error;
110
+ }
111
+ });
58
112
  // 분리 실행 = handoff 로 먼저 resolve. 이후 run 의 거부는 race 가 받는다 (미처리 거부 없음)
59
113
  return handedOff ? Promise.race([handedOff, run]) : run;
60
114
  }
@@ -81,6 +135,86 @@ export function authorize(entry, principal, options = {}) {
81
135
  if (missing.length > 0)
82
136
  deny("scope", `${entry.id} requires scopes: ${missing.join(", ")}.`);
83
137
  }
138
+ /**
139
+ * 실행 가드 (defineCommand authorize, §4). true 만 통과. 그 외 반환값 (undefined 포함) · 예외 = PERMISSION_DENIED (reason "authorize").
140
+ * CommandError = 그 message 로 거부. 그 외 예외 = 거부 + onCommandError 보고 (message 는 응답에 싣지 않는다).
141
+ * 취소 · mext 오류 (ctx.ask 의 INPUT_UNAVAILABLE 등) · 입력 대기 중의 예외는 바꾸지 않는다
142
+ */
143
+ async function guard(entry, command, input, ctx, options) {
144
+ if (!command.authorize)
145
+ return;
146
+ const denied = (message, cause) => {
147
+ const error = new MextError("PERMISSION_DENIED", message, {
148
+ command: entry.id,
149
+ reason: "authorize",
150
+ required_scopes: [...entry.command.scopes],
151
+ held_scopes: [...options.principal.scopes],
152
+ });
153
+ // 원인은 서버 쪽 디버깅용 (응답 본문 = toJSON 에는 없다)
154
+ if (cause !== undefined)
155
+ error.cause = cause;
156
+ return error;
157
+ };
158
+ let verdict;
159
+ try {
160
+ verdict = await command.authorize(input, ctx);
161
+ }
162
+ catch (error) {
163
+ if (ctx.signal.aborted || isMextError(error) || options.pendingInput?.())
164
+ throw error;
165
+ await reportCommandError(error, entry.id, options, ctx.signal);
166
+ throw denied(isCommandError(error) && error.message !== "" ? error.message : GUARD_DENIED(entry.id), error);
167
+ }
168
+ if (verdict !== true)
169
+ throw denied(GUARD_DENIED(entry.id));
170
+ }
171
+ /**
172
+ * 승인 (defineCommand needsApproval · requiresConfirmation, §4 승인). 승인이 필요한 호출을 표면의 확인 방식 한 곳에서 확인한다.
173
+ * 행위자 agent = 승인 불가 (APPROVAL_REQUIRED human_required). 묻지 않는다
174
+ * client 방식 표면 = 요청의 확인 표시 + 행위자 human 일 때만 통과. 묻지 않는다
175
+ * ask 방식 표면 (기본) = ctx.ask confirm (CLI = 프롬프트 · --yes, MCP = MRTR). ask 미주입 = INPUT_UNAVAILABLE. 확인 표시는 보지 않는다
176
+ * 승인 대상이 아닌 명령어 = 아무것도 하지 않는다
177
+ */
178
+ async function approve(entry, command, input, ctx, options, trail) {
179
+ if (!command.requiresConfirmation) {
180
+ if (!command.needsApproval)
181
+ return;
182
+ if (!(await approvalNeeded(entry, command, input, ctx, options))) {
183
+ trail?.approval({ required: false });
184
+ return;
185
+ }
186
+ }
187
+ const method = approvalModeOf(options.approval, ctx.caller.surface);
188
+ const actor = options.principal.actor?.kind;
189
+ trail?.approval({ required: true, method, confirmed: false });
190
+ if (actor === "agent" || (method === "client" && actor !== "human")) {
191
+ throw approvalRequired(entry.id, "human_required", { agent: actor === "agent" });
192
+ }
193
+ if (method === "client") {
194
+ if (options.confirmed !== true)
195
+ throw approvalRequired(entry.id, "unconfirmed");
196
+ }
197
+ else {
198
+ const confirmed = await ctx.ask({ kind: "confirm", message: confirmationMessage(entry, input) });
199
+ if (!confirmed)
200
+ throw inputDeclined(entry.id, "confirm");
201
+ ctx.signal.throwIfAborted();
202
+ }
203
+ trail?.approval({ required: true, method, confirmed: true });
204
+ }
205
+ /** needsApproval 조건식. false 만 승인 불필요. 그 외 반환값 · 예외 = 필요 (예외는 onCommandError 보고). 취소 · 입력 대기 중의 예외는 전파 */
206
+ async function approvalNeeded(entry, command, input, ctx, options) {
207
+ try {
208
+ return (await command.needsApproval(input, ctx)) !== false;
209
+ }
210
+ catch (error) {
211
+ if (ctx.signal.aborted || options.pendingInput?.())
212
+ throw error;
213
+ await reportCommandError(error, entry.id, options, ctx.signal);
214
+ return true;
215
+ }
216
+ }
217
+ const GUARD_DENIED = (id) => `${id} is not permitted for this caller.`;
84
218
  /** Zod safeParse 1회. 실패 = INVALID_ARGUMENT (missing_fields · issues · expected_schema · examples) */
85
219
  export async function parseInput(entry, rawInput) {
86
220
  const raw = rawInput === undefined ? {} : rawInput;
@@ -93,6 +227,12 @@ export async function parseInput(entry, rawInput) {
93
227
  * output 이 있으면 반환값 safeParse (모든 어댑터 · 노출 방식, §12.2). 결과 = 파싱된 값 (outputSchema 와 같은 모양).
94
228
  * 실패 = OutputValidationError (핸들러 예외와 같이 EXECUTION_ERROR · onCommandError 보고). 던진 오류는 검증하지 않는다
95
229
  */
230
+ /** execute 반환값 → 값 · 콘텐츠 (spec §4 결과 콘텐츠). output 검증 = 값만 */
231
+ async function settleOutput(id, command, raw) {
232
+ const { value, content } = splitResult(raw);
233
+ const parsed = await parseOutput(id, command, value);
234
+ return content ? { value: parsed, content } : { value: parsed };
235
+ }
96
236
  async function parseOutput(id, command, value) {
97
237
  if (!command.output)
98
238
  return value;
@@ -145,6 +285,7 @@ export function createContext(entry, options) {
145
285
  };
146
286
  return {
147
287
  principal: options.principal,
288
+ caller: callerOf(options.principal, options.transport ?? "local", options.client),
148
289
  signal: options.signal ?? new AbortController().signal,
149
290
  progress: options.progress ?? (() => { }),
150
291
  ask: ask,
@@ -168,7 +309,7 @@ function assertAskUrl(url) {
168
309
  throw new TypeError(`ctx.ask url must be an absolute http(s) URL: ${JSON.stringify(url)}`);
169
310
  }
170
311
  }
171
- /** requiresConfirmation 확인 문구. 검증된 입력을 포함 (무엇을 승인하는지) */
312
+ /** 승인 확인 문구 (ask 방식). 검증된 입력을 포함 (무엇을 승인하는지) */
172
313
  export function confirmationMessage(entry, input) {
173
314
  let args;
174
315
  try {