@mulsok/traders-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +103 -0
  2. package/bin/cli.js +160 -0
  3. package/bin/postinstall.js +57 -0
  4. package/bin/preuninstall.js +36 -0
  5. package/dist/server/broker/kiwoom/cache.js +86 -0
  6. package/dist/server/broker/kiwoom/cache.js.map +1 -0
  7. package/dist/server/broker/kiwoom/client.js +256 -0
  8. package/dist/server/broker/kiwoom/client.js.map +1 -0
  9. package/dist/server/broker/kiwoom/endpoints/_helpers.js +61 -0
  10. package/dist/server/broker/kiwoom/endpoints/_helpers.js.map +1 -0
  11. package/dist/server/broker/kiwoom/endpoints/account.js +448 -0
  12. package/dist/server/broker/kiwoom/endpoints/account.js.map +1 -0
  13. package/dist/server/broker/kiwoom/endpoints/detail.js +118 -0
  14. package/dist/server/broker/kiwoom/endpoints/detail.js.map +1 -0
  15. package/dist/server/broker/kiwoom/endpoints/investor.js +139 -0
  16. package/dist/server/broker/kiwoom/endpoints/investor.js.map +1 -0
  17. package/dist/server/broker/kiwoom/endpoints/order.js +134 -0
  18. package/dist/server/broker/kiwoom/endpoints/order.js.map +1 -0
  19. package/dist/server/broker/kiwoom/endpoints/quote.js +165 -0
  20. package/dist/server/broker/kiwoom/endpoints/quote.js.map +1 -0
  21. package/dist/server/broker/kiwoom/endpoints/ranking.js +180 -0
  22. package/dist/server/broker/kiwoom/endpoints/ranking.js.map +1 -0
  23. package/dist/server/broker/kiwoom/endpoints/sector.js +135 -0
  24. package/dist/server/broker/kiwoom/endpoints/sector.js.map +1 -0
  25. package/dist/server/broker/kiwoom/endpoints/theme.js +104 -0
  26. package/dist/server/broker/kiwoom/endpoints/theme.js.map +1 -0
  27. package/dist/server/broker/kiwoom/endpoints/universe.js +119 -0
  28. package/dist/server/broker/kiwoom/endpoints/universe.js.map +1 -0
  29. package/dist/server/broker/kiwoom/index.js +59 -0
  30. package/dist/server/broker/kiwoom/index.js.map +1 -0
  31. package/dist/server/broker/kiwoom/order-tracker.js +353 -0
  32. package/dist/server/broker/kiwoom/order-tracker.js.map +1 -0
  33. package/dist/server/broker/kiwoom/price-feed.js +119 -0
  34. package/dist/server/broker/kiwoom/price-feed.js.map +1 -0
  35. package/dist/server/broker/kiwoom/rate-limiter.js +97 -0
  36. package/dist/server/broker/kiwoom/rate-limiter.js.map +1 -0
  37. package/dist/server/broker/kiwoom/types.js +13 -0
  38. package/dist/server/broker/kiwoom/types.js.map +1 -0
  39. package/dist/server/broker/kiwoom/ws/client.js +370 -0
  40. package/dist/server/broker/kiwoom/ws/client.js.map +1 -0
  41. package/dist/server/broker/kiwoom/ws/endpoints/condition.js +146 -0
  42. package/dist/server/broker/kiwoom/ws/endpoints/condition.js.map +1 -0
  43. package/dist/server/broker/kiwoom/ws/realtime-bus.js +42 -0
  44. package/dist/server/broker/kiwoom/ws/realtime-bus.js.map +1 -0
  45. package/dist/server/broker/kiwoom/ws/types.js +19 -0
  46. package/dist/server/broker/kiwoom/ws/types.js.map +1 -0
  47. package/dist/server/broker/news.js +34 -0
  48. package/dist/server/broker/news.js.map +1 -0
  49. package/dist/server/bundle.js +43 -0
  50. package/dist/server/bundle.js.map +1 -0
  51. package/dist/server/calendar/krx-holidays.js +162 -0
  52. package/dist/server/calendar/krx-holidays.js.map +1 -0
  53. package/dist/server/config.js +263 -0
  54. package/dist/server/config.js.map +1 -0
  55. package/dist/server/db/sqlite.js +252 -0
  56. package/dist/server/db/sqlite.js.map +1 -0
  57. package/dist/server/diary/writer.js +266 -0
  58. package/dist/server/diary/writer.js.map +1 -0
  59. package/dist/server/index.js +316 -0
  60. package/dist/server/index.js.map +1 -0
  61. package/dist/server/jobs/universe-sync.js +132 -0
  62. package/dist/server/jobs/universe-sync.js.map +1 -0
  63. package/dist/server/jobs/watchdog.js +87 -0
  64. package/dist/server/jobs/watchdog.js.map +1 -0
  65. package/dist/server/journal/pnl-stats.js +108 -0
  66. package/dist/server/journal/pnl-stats.js.map +1 -0
  67. package/dist/server/journal/telemetry.js +174 -0
  68. package/dist/server/journal/telemetry.js.map +1 -0
  69. package/dist/server/journal/trade-journal.js +239 -0
  70. package/dist/server/journal/trade-journal.js.map +1 -0
  71. package/dist/server/llm/anthropic.js +98 -0
  72. package/dist/server/llm/anthropic.js.map +1 -0
  73. package/dist/server/llm/claude-cli.js +204 -0
  74. package/dist/server/llm/claude-cli.js.map +1 -0
  75. package/dist/server/llm/context-builder.js +229 -0
  76. package/dist/server/llm/context-builder.js.map +1 -0
  77. package/dist/server/llm/gemini.js +86 -0
  78. package/dist/server/llm/gemini.js.map +1 -0
  79. package/dist/server/llm/index.js +36 -0
  80. package/dist/server/llm/index.js.map +1 -0
  81. package/dist/server/llm/openai.js +87 -0
  82. package/dist/server/llm/openai.js.map +1 -0
  83. package/dist/server/personas/executor.js +318 -0
  84. package/dist/server/personas/executor.js.map +1 -0
  85. package/dist/server/personas/loader.js +165 -0
  86. package/dist/server/personas/loader.js.map +1 -0
  87. package/dist/server/personas/persona-agent.js +386 -0
  88. package/dist/server/personas/persona-agent.js.map +1 -0
  89. package/dist/server/personas/persona-state.js +170 -0
  90. package/dist/server/personas/persona-state.js.map +1 -0
  91. package/dist/server/personas/runner.js +162 -0
  92. package/dist/server/personas/runner.js.map +1 -0
  93. package/dist/server/personas/schema.js +123 -0
  94. package/dist/server/personas/schema.js.map +1 -0
  95. package/dist/server/personas/wake-plan.js +313 -0
  96. package/dist/server/personas/wake-plan.js.map +1 -0
  97. package/dist/server/readiness.js +414 -0
  98. package/dist/server/readiness.js.map +1 -0
  99. package/dist/server/routes.js +1216 -0
  100. package/dist/server/routes.js.map +1 -0
  101. package/dist/server/safety.js +153 -0
  102. package/dist/server/safety.js.map +1 -0
  103. package/dist/server/screener/engine.js +856 -0
  104. package/dist/server/screener/engine.js.map +1 -0
  105. package/dist/server/server-sync.js +427 -0
  106. package/dist/server/server-sync.js.map +1 -0
  107. package/dist/server/signing.js +39 -0
  108. package/dist/server/signing.js.map +1 -0
  109. package/dist/server/watchers/condition-watcher.js +519 -0
  110. package/dist/server/watchers/condition-watcher.js.map +1 -0
  111. package/dist/server/watchers/types.js +16 -0
  112. package/dist/server/watchers/types.js.map +1 -0
  113. package/dist/web/assets/index-62SMpbaf.js +79 -0
  114. package/dist/web/assets/index-BPLQR0wt.css +1 -0
  115. package/dist/web/index.html +14 -0
  116. package/package.json +93 -0
  117. package/scripts/com.mulsok.traders.client.plist.template +58 -0
  118. package/scripts/install-daemon.sh +156 -0
  119. package/scripts/uninstall-daemon.sh +62 -0
@@ -0,0 +1,87 @@
1
+ /** OpenAI finish_reason → 표준 LlmStopReason 매핑 */
2
+ function mapOpenAIStopReason(raw) {
3
+ switch (raw) {
4
+ case "stop": return "stop";
5
+ case "length": return "max_tokens";
6
+ case "tool_calls": return "tool_use";
7
+ case "function_call": return "tool_use";
8
+ case "content_filter": return "safety";
9
+ default: return "unknown";
10
+ }
11
+ }
12
+ const BASE = "https://api.openai.com/v1";
13
+ /**
14
+ * OpenAI 기본 max_tokens — 응답 잘림 방지 (사용자 정정 · 2026-05-03).
15
+ * gpt-4o · gpt-4o-mini 의 model 한계 (16384) 까지 자유 출력 허용.
16
+ * fetch timeout 없음 (무한대 대기).
17
+ */
18
+ const DEFAULT_MAX_TOKENS = 16384;
19
+ export function createOpenAIProvider(opts) {
20
+ return {
21
+ kind: "openai",
22
+ async test() {
23
+ if (!opts.apiKey)
24
+ return { ok: false, provider: "openai", error: "apiKey 없음" };
25
+ try {
26
+ const res = await fetch(`${BASE}/models`, {
27
+ headers: { authorization: `Bearer ${opts.apiKey}` },
28
+ });
29
+ if (!res.ok) {
30
+ return {
31
+ ok: false,
32
+ provider: "openai",
33
+ error: `HTTP ${res.status}: ${(await res.text()).slice(0, 120)}`,
34
+ };
35
+ }
36
+ const data = (await res.json());
37
+ const has = data.data?.some((m) => m.id === opts.model);
38
+ return {
39
+ ok: !!has,
40
+ provider: "openai",
41
+ model: opts.model,
42
+ detail: has ? "model 확인됨" : "model 목록에 없음",
43
+ };
44
+ }
45
+ catch (e) {
46
+ return { ok: false, provider: "openai", error: String(e) };
47
+ }
48
+ },
49
+ async complete(c) {
50
+ if (!opts.apiKey)
51
+ throw new Error("OpenAI apiKey 없음");
52
+ const res = await fetch(`${BASE}/chat/completions`, {
53
+ method: "POST",
54
+ headers: {
55
+ "content-type": "application/json",
56
+ authorization: `Bearer ${opts.apiKey}`,
57
+ },
58
+ body: JSON.stringify({
59
+ model: opts.model,
60
+ messages: [
61
+ ...(c.systemPrompt ? [{ role: "system", content: c.systemPrompt }] : []),
62
+ { role: "user", content: c.userPrompt },
63
+ ],
64
+ max_tokens: c.maxTokens ?? DEFAULT_MAX_TOKENS,
65
+ temperature: c.temperature ?? 0.2,
66
+ }),
67
+ });
68
+ if (!res.ok)
69
+ throw new Error(`OpenAI HTTP ${res.status}: ${await res.text()}`);
70
+ const data = (await res.json());
71
+ return {
72
+ text: data.choices?.[0]?.message?.content ?? "",
73
+ model: opts.model,
74
+ provider: "openai",
75
+ stopReason: mapOpenAIStopReason(data.choices?.[0]?.finish_reason),
76
+ usage: data.usage
77
+ ? {
78
+ inputTokens: data.usage.prompt_tokens ?? 0,
79
+ outputTokens: data.usage.completion_tokens ?? 0,
80
+ }
81
+ : undefined,
82
+ raw: data,
83
+ };
84
+ },
85
+ };
86
+ }
87
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src-server/llm/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Persona executor · 페르소나 단일 실행 사이클
3
+ *
4
+ * 흐름 (SPEC-10):
5
+ * 1. checkLlmCallSafety (emergency stop)
6
+ * 2. runPersonaScreening (frontmatter 기반)
7
+ * 3. Bundle (common + persona) + Layer 3 header + patches 조립
8
+ * 4. LLM 호출
9
+ * 5. logDecision (journal 기록)
10
+ * 6. extractAction → action="buy"/"sell" 시 safety 체크 + placeOrderWithTracking 호출
11
+ * 7. extractWakePlan → ConditionWatcher 자동 등록
12
+ * 8. 결과 반환
13
+ *
14
+ * 사용처:
15
+ * - HTTP API (/api/screen/:persona/decide) · routes.ts 에서 호출
16
+ * - ConditionWatcher 트리거 콜백 · index.ts 에서 호출
17
+ *
18
+ * 자율성 원칙 (SPEC-10): rate limit 폐기 (키움 broker rate limit 만 유효).
19
+ */
20
+ import { fetchCommonBundle, fetchPersonaBundle, assembleFullPrompt } from "../bundle.js";
21
+ import { loadConfig, SERVER_BASE_URL } from "../config.js";
22
+ import { createLlmProvider } from "../llm/index.js";
23
+ import { collectLayer3Snapshot, formatLayer3Markdown, loadActivePatches } from "../llm/context-builder.js";
24
+ import { logDecision, logOrderRejected } from "../journal/trade-journal.js";
25
+ import { buildPersonaCandidatesContext, runPersonaScreening } from "./runner.js";
26
+ import { extractWakePlan, registerWakePlan } from "./wake-plan.js";
27
+ import { placeOrderWithTracking } from "../broker/kiwoom/index.js";
28
+ import { checkLlmCallSafety, checkOrderSafety } from "../safety.js";
29
+ export function extractDecidedAction(rawText) {
30
+ if (!rawText)
31
+ return null;
32
+ try {
33
+ const m = rawText.match(/```(?:json)?\s*([\s\S]*?)```/);
34
+ const t = m ? m[1] : rawText;
35
+ const obj = JSON.parse(t);
36
+ const action = obj.action;
37
+ if (!["buy", "sell", "skip", "hold"].includes(action))
38
+ return null;
39
+ return {
40
+ action,
41
+ symbolCode: typeof obj.symbol_code === "string" ? obj.symbol_code : undefined,
42
+ symbolName: typeof obj.symbol_name === "string" ? obj.symbol_name : undefined,
43
+ quantity: typeof obj.quantity === "number" ? obj.quantity : undefined,
44
+ targetPriceKrw: typeof obj.target_price_krw === "number" ? obj.target_price_krw : undefined,
45
+ };
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /* ─────────── 메인 실행기 ─────────── */
52
+ //
53
+ // 자율성 원칙 (사용자 정정 · 2026-05-02 · SPEC-10):
54
+ // - executor 는 LLM 호출 횟수·간격에 어떤 제약도 강제하지 않음
55
+ // - 유일한 rate limit = 키움 REST API (broker 레이어 · SPEC-13 TokenBucket)
56
+ // - 무한 루프 방어는 wake-plan zod schema (SPEC-4) 의 minimum interval 로 처리
57
+ export async function executePersonaDecision(personaSlug, opts = {}) {
58
+ const triggeredBy = opts.triggeredBy;
59
+ // Emergency Stop 체크 (SPEC-26)
60
+ const llmGuard = checkLlmCallSafety();
61
+ if (!llmGuard.ok) {
62
+ return { ok: false, stage: "llm", error: `safety:${llmGuard.code} · ${llmGuard.reason}`, triggeredBy };
63
+ }
64
+ // LLM provider
65
+ const cfg = loadConfig();
66
+ const prov = createLlmProvider(cfg);
67
+ if (!prov) {
68
+ return { ok: false, stage: "llm", error: "LLM provider 미설정", triggeredBy };
69
+ }
70
+ // 스크리닝
71
+ const screening = await runPersonaScreening(personaSlug);
72
+ if (!screening) {
73
+ return { ok: false, stage: "persona", error: `persona '${personaSlug}' 미존재`, triggeredBy };
74
+ }
75
+ if (screening.skipped) {
76
+ return {
77
+ ok: false,
78
+ stage: "persona",
79
+ error: `persona screener skipped · ${screening.skipReason ?? ""}`,
80
+ persona: screening.persona,
81
+ triggeredBy,
82
+ };
83
+ }
84
+ // Bundle 조립
85
+ // 페르소나 = server (Supabase) 단일 진실 소스 (ADR-009).
86
+ // local yaml 만 있는 페르소나는 정식 페르소나가 아니므로 server bundle fetch 실패 시 그대로 에러.
87
+ // Layer3 snapshot 은 prompt 와 journal 양쪽에 사용 (외부 API 1회만).
88
+ let systemPrompt;
89
+ let layer3Snapshot = null;
90
+ try {
91
+ const common = await fetchCommonBundle(SERVER_BASE_URL);
92
+ const personaB = await fetchPersonaBundle(SERVER_BASE_URL, personaSlug, cfg.auth?.deviceToken);
93
+ const layer2Body = personaB.bundle.body;
94
+ layer3Snapshot = await collectLayer3Snapshot({ personaSlug });
95
+ const baseLayer3 = formatLayer3Markdown(layer3Snapshot);
96
+ const candidatesSection = buildPersonaCandidatesContext(screening, opts.topN ?? 8);
97
+ const layer3 = triggeredBy
98
+ ? `${baseLayer3}\n\n## 트리거 이벤트\n\n이번 실행은 감시자 트리거에 의해 발생했습니다.\n- Watcher ID: \`${triggeredBy.watcherId}\`\n- Intent: ${triggeredBy.intent}\n\n${candidatesSection}`
99
+ : `${baseLayer3}\n\n${candidatesSection}`;
100
+ const layer3Patches = loadActivePatches(personaSlug);
101
+ systemPrompt = assembleFullPrompt({
102
+ layer1: common.bundle.body,
103
+ layer2: layer2Body,
104
+ layer3Header: layer3,
105
+ layer3Patches: layer3Patches || undefined,
106
+ });
107
+ }
108
+ catch (e) {
109
+ return { ok: false, stage: "llm", error: `bundle 조립 실패: ${String(e)}`, persona: screening.persona, triggeredBy };
110
+ }
111
+ // LLM 호출
112
+ // - maxTokens 명시 X → provider 의 모델별 default 사용 (Anthropic 8192 / OpenAI 16384 / Gemini 8192)
113
+ // - 사용자 정정 (2026-05-03): timeout/maxTokens 제한 → 답변 잘림 → 재호출 비용 더 큼 → 없어야 한다.
114
+ // - 잘림 (max_tokens) 감지 시 journal 에 truncated:true + 경고 로그 (사후 추적).
115
+ let llmRes;
116
+ try {
117
+ llmRes = await prov.complete({
118
+ systemPrompt,
119
+ userPrompt: triggeredBy
120
+ ? `감시자 트리거로 재호출되었습니다 (${triggeredBy.intent}). 현재 상황을 종합해 오늘 거래 의사결정을 하나의 JSON 으로 응답하세요. action / symbol_code (있을 때) / reasoning / wake_plan / neoul 객체 포함.`
121
+ : "위 스크리닝 결과를 종합해서 오늘 거래 의사결정을 하나의 JSON 으로 응답하세요. action / symbol_code (있을 때) / reasoning / wake_plan / neoul 객체 포함.",
122
+ });
123
+ }
124
+ catch (e) {
125
+ return { ok: false, stage: "llm", error: `LLM 호출 실패: ${String(e)}`, persona: screening.persona, triggeredBy };
126
+ }
127
+ // 잘림 감지 — max_tokens / safety / unknown 일 때 경고
128
+ const truncated = llmRes.stopReason === "max_tokens";
129
+ if (truncated) {
130
+ console.warn(`[executor] ⚠️ ${personaSlug} LLM 응답이 max_tokens cap 에 막혀 잘림 (model=${llmRes.model} · output=${llmRes.usage?.outputTokens ?? "?"} tokens)`);
131
+ }
132
+ else if (llmRes.stopReason === "safety") {
133
+ console.warn(`[executor] ⚠️ ${personaSlug} LLM safety filter 차단 (model=${llmRes.model})`);
134
+ }
135
+ // Journal 기록 (전략 리뷰 풍부 컨텍스트 · SPEC-9 line 54-62)
136
+ // - reasoning 전체 보존 (truncate 제거 · 긴 추론도 사후 분석 가능)
137
+ // - top 8 후보 마다 rawMetrics + matchedPatterns + humanSummary + reasons (정량 + 자연어 동시)
138
+ // - diagnostics (rejectedBy + patternReject) — "후보 적은 이유" 기록
139
+ // - layer3_snapshot — LLM 호출 시점의 시장·보유·미체결·watcher 상태 재현용
140
+ // - stop_reason + truncated — 잘림 감지 (사후 비정상 case 추적)
141
+ logDecision({
142
+ personaSlug,
143
+ llmContext: {
144
+ reasoning: llmRes.text,
145
+ model: llmRes.model,
146
+ tokens: llmRes.usage,
147
+ stopReason: llmRes.stopReason,
148
+ truncated,
149
+ triggered_by: triggeredBy ?? null,
150
+ screening_summary: {
151
+ universe: screening.screener.universeSize,
152
+ evaluated: screening.screener.evaluated,
153
+ candidates: screening.screener.candidates.length,
154
+ skippedNoCandles: screening.screener.skippedNoCandles,
155
+ elapsedMs: screening.screener.elapsedMs,
156
+ diagnostics: {
157
+ rejectedBy: screening.screener.diagnostics.rejectedBy,
158
+ patternReject: screening.screener.diagnostics.patternReject,
159
+ samples: screening.screener.diagnostics.samples,
160
+ },
161
+ top: screening.screener.candidates.slice(0, 8).map((c) => ({
162
+ code: c.code,
163
+ name: c.name,
164
+ score: c.score,
165
+ matchedPatterns: c.matchedPatterns,
166
+ rawMetrics: c.rawMetrics,
167
+ humanSummary: c.humanSummaries.join(" / "),
168
+ reasons: c.reasons,
169
+ ma: c.ma,
170
+ todayChangePct: c.todayChangePct,
171
+ lastPriceKrw: c.lastPriceKrw,
172
+ })),
173
+ },
174
+ layer3_snapshot: layer3Snapshot,
175
+ },
176
+ });
177
+ // 주문 실행 (action="buy"/"sell" 시) — SPEC-10 + SPEC-26 safety 게이트
178
+ let orderMeta;
179
+ const decided = extractDecidedAction(llmRes.text);
180
+ if (decided && (decided.action === "buy" || decided.action === "sell")) {
181
+ if (!decided.symbolCode || !decided.quantity) {
182
+ orderMeta = { sent: false, reason: `LLM action=${decided.action} 인데 symbol_code 또는 quantity 누락` };
183
+ console.warn(`[executor] ${personaSlug} 주문 skip · ${orderMeta.reason}`);
184
+ }
185
+ else {
186
+ const safety = await checkOrderSafety({
187
+ personaSlug,
188
+ symbolCode: decided.symbolCode,
189
+ side: decided.action,
190
+ quantity: decided.quantity,
191
+ priceKrw: decided.targetPriceKrw,
192
+ });
193
+ if (!safety.ok) {
194
+ orderMeta = { sent: false, reason: `safety:${safety.code} · ${safety.reason}` };
195
+ console.warn(`[executor] ${personaSlug} 주문 차단 · ${orderMeta.reason}`);
196
+ logOrderRejected({
197
+ personaSlug,
198
+ symbolCode: decided.symbolCode,
199
+ side: decided.action,
200
+ quantity: decided.quantity,
201
+ priceKrw: decided.targetPriceKrw ?? 0,
202
+ clientOrderId: `safety_${Date.now()}`,
203
+ error: `safety:${safety.code} · ${safety.reason}`,
204
+ });
205
+ }
206
+ else {
207
+ try {
208
+ const placed = await placeOrderWithTracking({
209
+ symbolCode: decided.symbolCode,
210
+ side: decided.action,
211
+ quantity: decided.quantity,
212
+ priceKrw: decided.targetPriceKrw,
213
+ type: decided.targetPriceKrw ? "limit" : "market",
214
+ }, { personaSlug });
215
+ if (placed.ok) {
216
+ orderMeta = {
217
+ sent: true,
218
+ clientOrderId: placed.data.clientOrderId,
219
+ brokerOrderNo: placed.data.brokerOrderNo,
220
+ status: placed.data.status,
221
+ };
222
+ console.log(`[executor] ${personaSlug} 주문 접수 · ${decided.action} ${decided.symbolCode} ${decided.quantity}주 · ${placed.data.clientOrderId}`);
223
+ }
224
+ else {
225
+ orderMeta = { sent: false, reason: `broker reject · ${placed.error.message}` };
226
+ console.warn(`[executor] ${personaSlug} broker reject · ${placed.error.message}`);
227
+ }
228
+ }
229
+ catch (e) {
230
+ orderMeta = { sent: false, reason: `exception · ${String(e)}` };
231
+ console.error(`[executor] ${personaSlug} 주문 예외:`, e);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ else if (decided) {
237
+ // skip / hold — 기록만
238
+ orderMeta = { sent: false, reason: `LLM action=${decided.action}` };
239
+ }
240
+ else {
241
+ orderMeta = { sent: false, reason: "LLM 응답에서 action 파싱 실패" };
242
+ }
243
+ // wake_plan 파싱 + 자동 등록
244
+ let wakePlanMeta;
245
+ const extracted = extractWakePlan(llmRes.text);
246
+ if (!extracted.ok) {
247
+ wakePlanMeta = {
248
+ parsed: null,
249
+ registered: 0,
250
+ skipped: [],
251
+ nextScheduledRegistered: false,
252
+ parseError: extracted.error,
253
+ };
254
+ console.warn(`[executor] ${personaSlug} wake_plan 파싱 실패: ${extracted.error}`);
255
+ }
256
+ else if (extracted.wakePlan) {
257
+ const reg = registerWakePlan(personaSlug, extracted.wakePlan);
258
+ wakePlanMeta = {
259
+ parsed: extracted.wakePlan,
260
+ registered: reg.registered.length,
261
+ skipped: reg.skipped,
262
+ nextScheduledRegistered: reg.nextScheduledRegistered,
263
+ };
264
+ console.log(`[executor] ${personaSlug} wake_plan 등록 · time/price/event ${reg.registered.length} · skipped ${reg.skipped.length} · next_scheduled ${reg.nextScheduledRegistered ? "✓" : "-"}`);
265
+ }
266
+ // SPEC §6.1 + Layer 1 c1.8.0 §wake_plan 가드:
267
+ // wake_plan 누락 + tool call 로도 active watcher 등록 X 면 페르소나가 잠들 위험.
268
+ // 여기서는 비용 폭주 방지를 위해 자동 재호출은 하지 않고 강한 경고만 남긴다.
269
+ // 사용자/UI 가 cycle 결과의 wakePlan.silentDrift = true 를 보고 조치.
270
+ try {
271
+ const { getConditionWatcher } = await import("../watchers/condition-watcher.js");
272
+ const activeAfter = getConditionWatcher().list({ personaSlug, activeOnly: true }).length;
273
+ if ((!wakePlanMeta || wakePlanMeta.registered === 0) && activeAfter === 0) {
274
+ console.error(`[executor] ⚠ ${personaSlug} wake_plan 미등록 + active watcher 0 — 페르소나 잠듦 위험. ` +
275
+ `Layer 1 c1.8.0 §wake_plan 매 응답 필수.`);
276
+ wakePlanMeta = {
277
+ parsed: wakePlanMeta?.parsed ?? null,
278
+ registered: wakePlanMeta?.registered ?? 0,
279
+ skipped: wakePlanMeta?.skipped ?? [],
280
+ nextScheduledRegistered: wakePlanMeta?.nextScheduledRegistered ?? false,
281
+ parseError: wakePlanMeta?.parseError,
282
+ silentDrift: true,
283
+ };
284
+ }
285
+ }
286
+ catch {
287
+ // 가드 실패는 cycle 자체를 막지 않음.
288
+ }
289
+ return {
290
+ ok: true,
291
+ persona: screening.persona,
292
+ screening: {
293
+ universe: screening.screener.universeSize,
294
+ evaluated: screening.screener.evaluated,
295
+ candidates: screening.screener.candidates.length,
296
+ topCandidates: screening.screener.candidates.slice(0, opts.topN ?? 8),
297
+ },
298
+ llm: {
299
+ model: llmRes.model,
300
+ tokens: llmRes.usage,
301
+ response: llmRes.text,
302
+ assembled_length: systemPrompt.length,
303
+ },
304
+ wakePlan: wakePlanMeta,
305
+ order: orderMeta,
306
+ triggeredBy,
307
+ };
308
+ }
309
+ /* ─────────── 진단 ─────────── */
310
+ //
311
+ // 진단 메타는 trade-journal 의 readEvents() 에서 구할 수 있음.
312
+ // 별도 module-level Map 추적은 폐기 (SPEC-10 정정 · rate limit 폐기와 함께).
313
+ //
314
+ // e.g. 페르소나의 마지막 실행 시각 / 시간당 호출 수 조회:
315
+ // const events = readEvents({ eventTypes: ["decision"], personaSlug, from: oneHourAgo });
316
+ // const lastRunAt = events.at(-1)?.timestamp;
317
+ // const recentHourCount = events.length;
318
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src-server/personas/executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 페르소나 로더 · parser
3
+ *
4
+ * 입력 소스 (우선순위):
5
+ * 1. 서버 Bundle API 의 persona body (Supabase · 실 배포)
6
+ * └ body markdown 상단의 `---` frontmatter 를 추출
7
+ * 2. 로컬 override 파일 `~/.mulsok-traders/personas/<slug>.yaml`
8
+ * └ Supabase 배포 전 개발용 · frontmatter 만 저장 (본문 없음)
9
+ *
10
+ * 출력:
11
+ * PersonaDef = { slug, frontmatter, promptBody, source }
12
+ *
13
+ * 캐시:
14
+ * 60초 (빈번한 스크리닝 호출 고려 · Supabase 부담 경감)
15
+ */
16
+ import fs from "node:fs";
17
+ import os from "node:os";
18
+ import path from "node:path";
19
+ import matter from "gray-matter";
20
+ import { fetchPersonaBundle } from "../bundle.js";
21
+ import { SERVER_BASE_URL, loadConfig } from "../config.js";
22
+ import { PersonaFrontmatterSchema, } from "./schema.js";
23
+ const CACHE = new Map();
24
+ const CACHE_TTL_MS = 60_000;
25
+ const OVERRIDE_DIR = path.join(os.homedir(), ".mulsok-traders", "personas");
26
+ /**
27
+ * 페르소나 slug 로 정의 확보.
28
+ *
29
+ * @param slug 페르소나 슬러그 (예: "neoul")
30
+ * @returns PersonaDef · Supabase 가 있으면 그 frontmatter + 본문 · 없으면 로컬 override · 둘 다 없으면 null
31
+ */
32
+ export async function loadPersona(slug) {
33
+ const now = Date.now();
34
+ const cached = CACHE.get(slug);
35
+ if (cached && cached.expiresAt > now)
36
+ return cached.def;
37
+ const def = await loadPersonaFresh(slug);
38
+ if (def)
39
+ CACHE.set(slug, { def, expiresAt: now + CACHE_TTL_MS });
40
+ return def;
41
+ }
42
+ /** 캐시 무시 · 강제 재로드 */
43
+ export async function reloadPersona(slug) {
44
+ CACHE.delete(slug);
45
+ return loadPersona(slug);
46
+ }
47
+ async function loadPersonaFresh(slug) {
48
+ let supabaseFm = null;
49
+ let supabaseBody = "";
50
+ let rawBody;
51
+ let hashOk;
52
+ let version;
53
+ /* 1. Supabase 시도 (device_token 인증 — bundle endpoint 가 401 요구) */
54
+ try {
55
+ const cfg = loadConfig();
56
+ const bundleRes = await fetchPersonaBundle(SERVER_BASE_URL, slug, cfg.auth?.deviceToken);
57
+ rawBody = bundleRes.bundle.body;
58
+ hashOk = bundleRes.hash_ok;
59
+ version = bundleRes.bundle.version;
60
+ const parsed = parseMarkdownWithFrontmatter(rawBody);
61
+ if (parsed) {
62
+ supabaseFm = parsed.frontmatter;
63
+ supabaseBody = parsed.body;
64
+ }
65
+ else {
66
+ // frontmatter 없는 레거시 body · frontmatter 없이 그대로
67
+ supabaseBody = rawBody;
68
+ }
69
+ }
70
+ catch {
71
+ // Supabase 실패는 조용히 override 로 fallback
72
+ }
73
+ /* 2. 로컬 override */
74
+ const overrideFm = readLocalOverride(slug);
75
+ if (!supabaseFm && !overrideFm) {
76
+ // Supabase body 는 있지만 frontmatter 없고 override 도 없음 → 페르소나 스크리너 쓸 수 없음
77
+ if (supabaseBody) {
78
+ return {
79
+ slug,
80
+ frontmatter: {
81
+ persona: slug,
82
+ version: version ?? "unknown",
83
+ },
84
+ promptBody: supabaseBody,
85
+ source: "supabase",
86
+ rawBody,
87
+ hashOk,
88
+ version,
89
+ };
90
+ }
91
+ return null;
92
+ }
93
+ // override 가 있으면 supabase fm 위에 얹기 (override 가 우선)
94
+ const merged = overrideFm
95
+ ? { ...(supabaseFm ?? { persona: slug, version: version ?? "override" }), ...overrideFm }
96
+ : supabaseFm;
97
+ const source = overrideFm && supabaseFm
98
+ ? "mixed"
99
+ : overrideFm
100
+ ? "local-override"
101
+ : "supabase";
102
+ return {
103
+ slug,
104
+ frontmatter: merged,
105
+ promptBody: supabaseBody,
106
+ source,
107
+ rawBody,
108
+ hashOk,
109
+ version,
110
+ };
111
+ }
112
+ function readLocalOverride(slug) {
113
+ const yamlPath = path.join(OVERRIDE_DIR, `${slug}.yaml`);
114
+ const ymlPath = path.join(OVERRIDE_DIR, `${slug}.yml`);
115
+ const mdPath = path.join(OVERRIDE_DIR, `${slug}.md`);
116
+ const candidates = [yamlPath, ymlPath, mdPath];
117
+ for (const p of candidates) {
118
+ if (!fs.existsSync(p))
119
+ continue;
120
+ try {
121
+ const raw = fs.readFileSync(p, "utf8");
122
+ const frontmatter = p.endsWith(".md")
123
+ ? parseMarkdownWithFrontmatter(raw)?.frontmatter ?? null
124
+ : parseYamlAsFrontmatter(raw);
125
+ if (!frontmatter)
126
+ continue;
127
+ const res = PersonaFrontmatterSchema.safeParse({ persona: slug, version: "override", ...frontmatter });
128
+ if (!res.success) {
129
+ throw new Error(`Override frontmatter validation 실패 · ${slug}: ${formatZodError(res.error)}`);
130
+ }
131
+ return res.data;
132
+ }
133
+ catch (e) {
134
+ console.warn(`[personas] override 로드 실패 (${p}): ${String(e)}`);
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+ export function parseMarkdownWithFrontmatter(body) {
140
+ const trimmed = body.trimStart();
141
+ if (!trimmed.startsWith("---"))
142
+ return null;
143
+ const parsed = matter(body);
144
+ const fmRaw = parsed.data;
145
+ if (!fmRaw || Object.keys(fmRaw).length === 0)
146
+ return null;
147
+ const res = PersonaFrontmatterSchema.safeParse(fmRaw);
148
+ if (!res.success) {
149
+ throw new Error(`Persona frontmatter validation 실패: ${formatZodError(res.error)}`);
150
+ }
151
+ return { frontmatter: res.data, body: parsed.content };
152
+ }
153
+ function parseYamlAsFrontmatter(raw) {
154
+ // gray-matter 내부의 js-yaml 재사용 · 단독 yaml 을 "---\n...\n---" 로 감싸서 파싱
155
+ const wrapped = `---\n${raw.trim()}\n---\n`;
156
+ const parsed = matter(wrapped);
157
+ return parsed.data;
158
+ }
159
+ function formatZodError(err) {
160
+ return err.issues
161
+ .map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`)
162
+ .join(" · ");
163
+ }
164
+ export { OVERRIDE_DIR as PERSONA_OVERRIDE_DIR };
165
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src-server/personas/loader.ts"],"names":[],"mappings":"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"}