@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.
- package/README.md +103 -0
- package/bin/cli.js +160 -0
- package/bin/postinstall.js +57 -0
- package/bin/preuninstall.js +36 -0
- package/dist/server/broker/kiwoom/cache.js +86 -0
- package/dist/server/broker/kiwoom/cache.js.map +1 -0
- package/dist/server/broker/kiwoom/client.js +256 -0
- package/dist/server/broker/kiwoom/client.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/_helpers.js +61 -0
- package/dist/server/broker/kiwoom/endpoints/_helpers.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/account.js +448 -0
- package/dist/server/broker/kiwoom/endpoints/account.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/detail.js +118 -0
- package/dist/server/broker/kiwoom/endpoints/detail.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/investor.js +139 -0
- package/dist/server/broker/kiwoom/endpoints/investor.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/order.js +134 -0
- package/dist/server/broker/kiwoom/endpoints/order.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/quote.js +165 -0
- package/dist/server/broker/kiwoom/endpoints/quote.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/ranking.js +180 -0
- package/dist/server/broker/kiwoom/endpoints/ranking.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/sector.js +135 -0
- package/dist/server/broker/kiwoom/endpoints/sector.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/theme.js +104 -0
- package/dist/server/broker/kiwoom/endpoints/theme.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/universe.js +119 -0
- package/dist/server/broker/kiwoom/endpoints/universe.js.map +1 -0
- package/dist/server/broker/kiwoom/index.js +59 -0
- package/dist/server/broker/kiwoom/index.js.map +1 -0
- package/dist/server/broker/kiwoom/order-tracker.js +353 -0
- package/dist/server/broker/kiwoom/order-tracker.js.map +1 -0
- package/dist/server/broker/kiwoom/price-feed.js +119 -0
- package/dist/server/broker/kiwoom/price-feed.js.map +1 -0
- package/dist/server/broker/kiwoom/rate-limiter.js +97 -0
- package/dist/server/broker/kiwoom/rate-limiter.js.map +1 -0
- package/dist/server/broker/kiwoom/types.js +13 -0
- package/dist/server/broker/kiwoom/types.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/client.js +370 -0
- package/dist/server/broker/kiwoom/ws/client.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/endpoints/condition.js +146 -0
- package/dist/server/broker/kiwoom/ws/endpoints/condition.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/realtime-bus.js +42 -0
- package/dist/server/broker/kiwoom/ws/realtime-bus.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/types.js +19 -0
- package/dist/server/broker/kiwoom/ws/types.js.map +1 -0
- package/dist/server/broker/news.js +34 -0
- package/dist/server/broker/news.js.map +1 -0
- package/dist/server/bundle.js +43 -0
- package/dist/server/bundle.js.map +1 -0
- package/dist/server/calendar/krx-holidays.js +162 -0
- package/dist/server/calendar/krx-holidays.js.map +1 -0
- package/dist/server/config.js +263 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/db/sqlite.js +252 -0
- package/dist/server/db/sqlite.js.map +1 -0
- package/dist/server/diary/writer.js +266 -0
- package/dist/server/diary/writer.js.map +1 -0
- package/dist/server/index.js +316 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/jobs/universe-sync.js +132 -0
- package/dist/server/jobs/universe-sync.js.map +1 -0
- package/dist/server/jobs/watchdog.js +87 -0
- package/dist/server/jobs/watchdog.js.map +1 -0
- package/dist/server/journal/pnl-stats.js +108 -0
- package/dist/server/journal/pnl-stats.js.map +1 -0
- package/dist/server/journal/telemetry.js +174 -0
- package/dist/server/journal/telemetry.js.map +1 -0
- package/dist/server/journal/trade-journal.js +239 -0
- package/dist/server/journal/trade-journal.js.map +1 -0
- package/dist/server/llm/anthropic.js +98 -0
- package/dist/server/llm/anthropic.js.map +1 -0
- package/dist/server/llm/claude-cli.js +204 -0
- package/dist/server/llm/claude-cli.js.map +1 -0
- package/dist/server/llm/context-builder.js +229 -0
- package/dist/server/llm/context-builder.js.map +1 -0
- package/dist/server/llm/gemini.js +86 -0
- package/dist/server/llm/gemini.js.map +1 -0
- package/dist/server/llm/index.js +36 -0
- package/dist/server/llm/index.js.map +1 -0
- package/dist/server/llm/openai.js +87 -0
- package/dist/server/llm/openai.js.map +1 -0
- package/dist/server/personas/executor.js +318 -0
- package/dist/server/personas/executor.js.map +1 -0
- package/dist/server/personas/loader.js +165 -0
- package/dist/server/personas/loader.js.map +1 -0
- package/dist/server/personas/persona-agent.js +386 -0
- package/dist/server/personas/persona-agent.js.map +1 -0
- package/dist/server/personas/persona-state.js +170 -0
- package/dist/server/personas/persona-state.js.map +1 -0
- package/dist/server/personas/runner.js +162 -0
- package/dist/server/personas/runner.js.map +1 -0
- package/dist/server/personas/schema.js +123 -0
- package/dist/server/personas/schema.js.map +1 -0
- package/dist/server/personas/wake-plan.js +313 -0
- package/dist/server/personas/wake-plan.js.map +1 -0
- package/dist/server/readiness.js +414 -0
- package/dist/server/readiness.js.map +1 -0
- package/dist/server/routes.js +1216 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/safety.js +153 -0
- package/dist/server/safety.js.map +1 -0
- package/dist/server/screener/engine.js +856 -0
- package/dist/server/screener/engine.js.map +1 -0
- package/dist/server/server-sync.js +427 -0
- package/dist/server/server-sync.js.map +1 -0
- package/dist/server/signing.js +39 -0
- package/dist/server/signing.js.map +1 -0
- package/dist/server/watchers/condition-watcher.js +519 -0
- package/dist/server/watchers/condition-watcher.js.map +1 -0
- package/dist/server/watchers/types.js +16 -0
- package/dist/server/watchers/types.js.map +1 -0
- package/dist/web/assets/index-62SMpbaf.js +79 -0
- package/dist/web/assets/index-BPLQR0wt.css +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +93 -0
- package/scripts/com.mulsok.traders.client.plist.template +58 -0
- package/scripts/install-daemon.sh +156 -0
- package/scripts/uninstall-daemon.sh +62 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPEC-27 · Stateful Persona Agent — agent cycle 실행 (ADR-011).
|
|
3
|
+
*
|
|
4
|
+
* 모델: claude-cli (subprocess · multi-turn 도구 호출 자체 처리).
|
|
5
|
+
*
|
|
6
|
+
* 흐름:
|
|
7
|
+
* 1. config.llm.provider === "claude-cli" 검증
|
|
8
|
+
* 2. system prompt = Layer 1 + Layer 2 (server bundle 그대로)
|
|
9
|
+
* 3. user prompt = agent 작업 지시 (도구 사용 가이드 + 도메인 컨텍스트)
|
|
10
|
+
* 4. claude-cli subprocess 실행 (--add-dir 페르소나 폴더 + 프로젝트 root)
|
|
11
|
+
* 5. agent 가:
|
|
12
|
+
* - state.json + strategy.md Read
|
|
13
|
+
* - HTTP 도구 (curl localhost:PORT) 로 시세/스크리닝/holdings 등 조회
|
|
14
|
+
* - 의사결정 + state.json Write/Edit + strategy.md Edit
|
|
15
|
+
* - 필요시 placeOrder + registerWatcher 호출
|
|
16
|
+
* 6. journal 에 cycle 시작/종료 이벤트 기록
|
|
17
|
+
* 7. 결과 반환
|
|
18
|
+
*
|
|
19
|
+
* 안전:
|
|
20
|
+
* - SPEC-26 halted 시 즉시 차단
|
|
21
|
+
* - claude-cli 미설치 시 즉시 fail
|
|
22
|
+
* - 도구 호출 격리는 prompt 강제 + path 검증 (state.json 같은 파일은 페르소나 폴더 안만)
|
|
23
|
+
*/
|
|
24
|
+
import { spawn } from "node:child_process";
|
|
25
|
+
import os from "node:os";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import { loadConfig } from "../config.js";
|
|
28
|
+
import { checkLlmCallSafety } from "../safety.js";
|
|
29
|
+
import { fetchCommonBundle, fetchPersonaBundle, assembleFullPrompt } from "../bundle.js";
|
|
30
|
+
import { SERVER_BASE_URL } from "../config.js";
|
|
31
|
+
import { loadPersona } from "./loader.js";
|
|
32
|
+
import { loadState, personaDir } from "./persona-state.js";
|
|
33
|
+
import { logDecision } from "../journal/trade-journal.js";
|
|
34
|
+
const TIMEOUT_SEC = (() => {
|
|
35
|
+
const env = process.env.CLAUDE_CLI_TIMEOUT_SEC;
|
|
36
|
+
if (env && /^\d+$/.test(env)) {
|
|
37
|
+
const n = Number(env);
|
|
38
|
+
if (n >= 60 && n <= 3600)
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
41
|
+
return 30 * 60; // 기본 30분
|
|
42
|
+
})();
|
|
43
|
+
export async function executePersonaCycle(slug, triggeredBy = { kind: "manual" }) {
|
|
44
|
+
const startedAt = new Date().toISOString();
|
|
45
|
+
const t0 = Date.now();
|
|
46
|
+
// 1. Safety
|
|
47
|
+
const guard = checkLlmCallSafety();
|
|
48
|
+
if (!guard.ok) {
|
|
49
|
+
return failResult(slug, triggeredBy, startedAt, t0, "safety", `safety:${guard.code} · ${guard.reason}`);
|
|
50
|
+
}
|
|
51
|
+
// 2. config + provider
|
|
52
|
+
const cfg = loadConfig();
|
|
53
|
+
if (cfg.llm.provider !== "claude-cli") {
|
|
54
|
+
return failResult(slug, triggeredBy, startedAt, t0, "config", `agent cycle 은 LLM provider = "claude-cli" 만 지원 (현재 ${cfg.llm.provider}). config 변경 후 재시도.`);
|
|
55
|
+
}
|
|
56
|
+
const cliBinary = cfg.llm.claudeCli?.binaryPath ?? "claude";
|
|
57
|
+
const cliModel = cfg.llm.claudeCli?.model ?? "sonnet";
|
|
58
|
+
// 3. persona
|
|
59
|
+
const persona = await loadPersona(slug);
|
|
60
|
+
if (!persona) {
|
|
61
|
+
return failResult(slug, triggeredBy, startedAt, t0, "persona", `persona '${slug}' 미존재`);
|
|
62
|
+
}
|
|
63
|
+
// 4. Layer 1+2 prompt
|
|
64
|
+
let systemPrompt;
|
|
65
|
+
try {
|
|
66
|
+
const common = await fetchCommonBundle(SERVER_BASE_URL);
|
|
67
|
+
const personaB = await fetchPersonaBundle(SERVER_BASE_URL, slug, cfg.auth?.deviceToken);
|
|
68
|
+
systemPrompt = assembleFullPrompt({
|
|
69
|
+
layer1: common.bundle.body,
|
|
70
|
+
layer2: personaB.bundle.body,
|
|
71
|
+
layer3Header: agentInstructionLayer3(slug, triggeredBy),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
return failResult(slug, triggeredBy, startedAt, t0, "bundle", `bundle 조립 실패: ${String(e)}`);
|
|
76
|
+
}
|
|
77
|
+
// 5. claude-cli 실행
|
|
78
|
+
const projectRoot = process.env.MULSOK_PROJECT_ROOT ?? process.cwd();
|
|
79
|
+
const personaWorkDir = personaDir(slug);
|
|
80
|
+
const port = process.env.CLIENT_PORT ?? "8787";
|
|
81
|
+
const userPrompt = buildAgentUserPrompt(slug, triggeredBy, port);
|
|
82
|
+
const args = [
|
|
83
|
+
"--dangerously-skip-permissions",
|
|
84
|
+
"--add-dir", projectRoot,
|
|
85
|
+
"--add-dir", personaWorkDir, // 페르소나 폴더 read/write
|
|
86
|
+
"-p", userPrompt,
|
|
87
|
+
"--output-format", "json",
|
|
88
|
+
"--model", cliModel,
|
|
89
|
+
"--system-prompt", systemPrompt,
|
|
90
|
+
];
|
|
91
|
+
let stdout = "";
|
|
92
|
+
try {
|
|
93
|
+
const r = await runClaude(cliBinary, args, TIMEOUT_SEC);
|
|
94
|
+
stdout = r.stdout;
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return failResult(slug, triggeredBy, startedAt, t0, "agent", `claude-cli 실행 실패: ${String(e)}`);
|
|
98
|
+
}
|
|
99
|
+
const summary = extractAssistantText(stdout);
|
|
100
|
+
// 6. journal cycle 기록
|
|
101
|
+
try {
|
|
102
|
+
logDecision({
|
|
103
|
+
personaSlug: slug,
|
|
104
|
+
llmContext: {
|
|
105
|
+
reasoning: summary,
|
|
106
|
+
model: `claude-cli/${cliModel}`,
|
|
107
|
+
stopReason: "stop",
|
|
108
|
+
truncated: false,
|
|
109
|
+
triggered_by: triggeredBy.watcherId
|
|
110
|
+
? { intent: triggeredBy.intent ?? triggeredBy.kind, watcherId: triggeredBy.watcherId }
|
|
111
|
+
: null,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
console.warn(`[persona-agent] ${slug} journal 실패:`, e);
|
|
117
|
+
}
|
|
118
|
+
const stateAfter = loadState(slug);
|
|
119
|
+
const endedAt = new Date().toISOString();
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
slug,
|
|
123
|
+
triggeredBy,
|
|
124
|
+
startedAt,
|
|
125
|
+
endedAt,
|
|
126
|
+
elapsedMs: Date.now() - t0,
|
|
127
|
+
summary,
|
|
128
|
+
stateAfter,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function failResult(slug, triggeredBy, startedAt, t0, stage, error) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
slug,
|
|
135
|
+
triggeredBy,
|
|
136
|
+
startedAt,
|
|
137
|
+
endedAt: new Date().toISOString(),
|
|
138
|
+
elapsedMs: Date.now() - t0,
|
|
139
|
+
stage,
|
|
140
|
+
error,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/* ================================================================
|
|
144
|
+
Agent prompt — Layer 3 header + user prompt (도구 사용 가이드)
|
|
145
|
+
================================================================ */
|
|
146
|
+
function agentInstructionLayer3(slug, triggeredBy) {
|
|
147
|
+
const triggerLine = triggeredBy.kind === "watcher"
|
|
148
|
+
? `이번 사이클은 감시자 발화 트리거 (intent: ${triggeredBy.intent ?? "?"} · watcherId: ${triggeredBy.watcherId ?? "?"}).`
|
|
149
|
+
: triggeredBy.kind === "scheduled"
|
|
150
|
+
? "이번 사이클은 당신이 자체적으로 예약한 시각 (state.json.nextRunAt) 도달."
|
|
151
|
+
: triggeredBy.kind === "boot"
|
|
152
|
+
? "이번 사이클은 클라이언트 첫 시작."
|
|
153
|
+
: "이번 사이클은 사용자가 「지금 분석」 수동 트리거.";
|
|
154
|
+
return `## 현재 컨텍스트
|
|
155
|
+
|
|
156
|
+
당신은 페르소나 \`${slug}\` 입니다. ${triggerLine}
|
|
157
|
+
|
|
158
|
+
## 당신의 메모리
|
|
159
|
+
|
|
160
|
+
- 자기 전용 폴더: \`~/.mulsok-traders/personas/${slug}/\`
|
|
161
|
+
- \`state.json\` — watchlist · positions · stats · nextRunAt
|
|
162
|
+
- \`strategy.md\` — 자유 형식 전략 노트 (시장 판단 · 학습 등)
|
|
163
|
+
|
|
164
|
+
## 자율성 원칙
|
|
165
|
+
|
|
166
|
+
- 정기 cron 은 없습니다. 다음 사이클 시각은 당신이 \`state.json.nextRunAt\` (ISO8601) 으로 직접 결정.
|
|
167
|
+
- 또는 시세 조건 watcher 를 등록 (\`POST /api/watchers\`) 해서 트리거 받기.
|
|
168
|
+
- 둘 다 안 잡으면 시스템 watchdog 가 「잠들음」 alert 를 띄웁니다.
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
function buildAgentUserPrompt(slug, triggeredBy, port) {
|
|
172
|
+
return `다음 작업을 순서대로 수행하세요. 각 단계마다 도구를 직접 사용하세요.
|
|
173
|
+
|
|
174
|
+
# 1. 메모리 read
|
|
175
|
+
- \`Read\` 도구로 \`~/.mulsok-traders/personas/${slug}/state.json\` 읽기
|
|
176
|
+
- \`Read\` 도구로 \`~/.mulsok-traders/personas/${slug}/strategy.md\` 읽기
|
|
177
|
+
|
|
178
|
+
# 2. 시장 컨텍스트 조회 (필요한 만큼만 — 토큰 절약)
|
|
179
|
+
|
|
180
|
+
다음 HTTP 도구를 \`curl\` (Bash) 로 호출:
|
|
181
|
+
|
|
182
|
+
- 보유 종목: \`curl -s http://localhost:${port}/api/account/status\`
|
|
183
|
+
- 활성 watcher: \`curl -s 'http://localhost:${port}/api/watchers?personaSlug=${slug}&activeOnly=true'\`
|
|
184
|
+
- 최근 7일 이벤트: \`curl -s 'http://localhost:${port}/api/journal/events?days=7'\`
|
|
185
|
+
- 종목 일봉 (필요 시): \`curl -s 'http://localhost:${port}/api/quote/daily/<종목코드>?days=30'\`
|
|
186
|
+
- 종목 현재가: \`curl -s http://localhost:${port}/api/quote/current/<종목코드>\`
|
|
187
|
+
|
|
188
|
+
# 3. ★ Cleanup 단계 (필수 · 사이클 시작마다 반드시 수행)
|
|
189
|
+
|
|
190
|
+
이 단계는 **항상** 수행하세요. 누적 잔존 watcher 와 ETF/우선주가 watchlist 에 들어오는 것을 막습니다.
|
|
191
|
+
|
|
192
|
+
## 3-A. 활성 watcher 정리
|
|
193
|
+
|
|
194
|
+
위에서 받은 활성 watcher 리스트를 검사:
|
|
195
|
+
|
|
196
|
+
1. **같은 종목 + 같은 조건 (intent 가 거의 동일) 인 watcher 가 여러 개** 있으면 가장 최근 1개만 유지하고 나머지 제거:
|
|
197
|
+
\`\`\`
|
|
198
|
+
curl -s -X DELETE 'http://localhost:${port}/api/watchers/<id>'
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
2. **만료 임박 (24시간 내 expiresAt) + 트리거 조건이 현재가에서 멀어진** watcher 는 제거 후 필요하면 재등록.
|
|
202
|
+
|
|
203
|
+
3. **bulk 정리** (한 종목 모두 지우고 깨끗이 다시 등록):
|
|
204
|
+
\`\`\`
|
|
205
|
+
curl -s -X DELETE 'http://localhost:${port}/api/watchers?personaSlug=${slug}&symbolCode=<종목코드>&keepIds=wt_xxx'
|
|
206
|
+
\`\`\`
|
|
207
|
+
keepIds 는 유지할 watcher 의 id (콤마 구분, 비우면 전부 제거).
|
|
208
|
+
|
|
209
|
+
## 3-B. watchlist 검토 (state.json)
|
|
210
|
+
|
|
211
|
+
state.json 의 watchlist 를 순회하면서:
|
|
212
|
+
|
|
213
|
+
1. **종목명 검사 — 다음은 즉시 제거**:
|
|
214
|
+
- prefix: KODEX·TIGER·KBSTAR·ARIRANG·ACE·KOSEF·HANARO·SOL·PLUS·RISE 등 (ETF)
|
|
215
|
+
- 키워드 포함: ETF·ETN·ELS·ELW·인버스·레버리지 (파생)
|
|
216
|
+
- 「리츠」/「REITs」 (리츠)
|
|
217
|
+
- 「스팩」/「SPAC」 (스팩)
|
|
218
|
+
- 종목 코드 끝자리 5~9 (우선주)
|
|
219
|
+
- 종목 코드에 알파벳 (예: 0148J0 — 액티브 ETF)
|
|
220
|
+
2. **expiresAt 지난 항목 제거**
|
|
221
|
+
3. **트리거 가격이 현재가와 너무 동떨어진 항목** (예: -30% 이상) 은 재평가 또는 제거
|
|
222
|
+
|
|
223
|
+
# 4. 신규 후보 발굴 (스크리닝 · 자율 판단)
|
|
224
|
+
|
|
225
|
+
**screen 호출 여부는 당신이 판단합니다**. 매번 호출 X.
|
|
226
|
+
|
|
227
|
+
다음 경우에만 호출:
|
|
228
|
+
- watchlist 가 5개 이하로 줄었을 때
|
|
229
|
+
- 마지막 발굴이 1일 이상 지났을 때
|
|
230
|
+
- 시장 상황이 크게 바뀌어 (지수 급변·테마 주도주 변경 등) 재평가 필요할 때
|
|
231
|
+
- 사용자가 「↻ 지금 분석」 수동 트리거 시
|
|
232
|
+
|
|
233
|
+
호출 시:
|
|
234
|
+
\`\`\`
|
|
235
|
+
curl -s 'http://localhost:${port}/api/screen/${slug}?topN=20'
|
|
236
|
+
\`\`\`
|
|
237
|
+
|
|
238
|
+
응답 \`screener.universeSize == 0\` 이면 데이터 미적재:
|
|
239
|
+
\`\`\`
|
|
240
|
+
# 1500 종목 backfill (3~5분)
|
|
241
|
+
curl -s -X POST http://localhost:${port}/api/local/sync \\
|
|
242
|
+
-H 'content-type: application/json' \\
|
|
243
|
+
-d '{"maxCodes": 200, "candleDays": 120}'
|
|
244
|
+
|
|
245
|
+
# 또는 양음양 타겟 배치 (~1분)
|
|
246
|
+
curl -s -X POST http://localhost:${port}/api/local/sync/yinyang \\
|
|
247
|
+
-H 'content-type: application/json' \\
|
|
248
|
+
-d '{"candleDays": 120}'
|
|
249
|
+
\`\`\`
|
|
250
|
+
|
|
251
|
+
장 마감 후 (15:30 KST) 라면 D-0 포함 데이터로 내일 거래 후보 미리 잡기.
|
|
252
|
+
|
|
253
|
+
# 5. 의사결정 (당신의 전략에 따라)
|
|
254
|
+
|
|
255
|
+
state.json 의 watchlist + 현재 시장 + 보유 + 활성 watcher 를 종합:
|
|
256
|
+
|
|
257
|
+
- watchlist 의 트리거 가격 도달 여부 확인 → 매수 검토
|
|
258
|
+
- 보유 종목의 손절·익절 조건 충족 여부 → 매도 검토
|
|
259
|
+
- 신규 발굴 결과가 흥미로우면 watchlist 추가 + watcher 등록
|
|
260
|
+
|
|
261
|
+
# 6. 행동 (자율 결정)
|
|
262
|
+
|
|
263
|
+
필요한 행동만 하세요. 모든 단계는 선택사항:
|
|
264
|
+
|
|
265
|
+
- **매수/매도 주문** (실거래·모의 둘 다 안전 게이트 통과 필요):
|
|
266
|
+
\`\`\`
|
|
267
|
+
curl -s -X POST http://localhost:${port}/api/orders \\
|
|
268
|
+
-H 'content-type: application/json' \\
|
|
269
|
+
-d '{"personaSlug":"${slug}","symbolCode":"...","side":"buy","quantity":N,"priceKrw":N}'
|
|
270
|
+
\`\`\`
|
|
271
|
+
- **시세 watcher 등록** (다음 깨우기 조건):
|
|
272
|
+
\`\`\`
|
|
273
|
+
curl -s -X POST http://localhost:${port}/api/watchers \\
|
|
274
|
+
-H 'content-type: application/json' \\
|
|
275
|
+
-d '{"personaSlug":"${slug}","watchers":[{"intent":"...","condition":{...},"wakeAction":"...","maxWaitHours":N}]}'
|
|
276
|
+
\`\`\`
|
|
277
|
+
|
|
278
|
+
# 7. 메모리 갱신 (필수)
|
|
279
|
+
|
|
280
|
+
- \`Write\` 도구로 \`~/.mulsok-traders/personas/${slug}/state.json\` 갱신
|
|
281
|
+
- watchlist 추가/제거
|
|
282
|
+
- positions 동기 (주문 후)
|
|
283
|
+
- stats 갱신 (매도 후 손익)
|
|
284
|
+
- **\`nextRunAt\` 설정** (ISO8601 · 한국 영업일 고려) — 또는 watcher 만 등록했으면 \`null\`
|
|
285
|
+
- \`Edit\` 도구로 \`~/.mulsok-traders/personas/${slug}/strategy.md\` 갱신
|
|
286
|
+
- 「현재 시장 판단」 섹션 갱신
|
|
287
|
+
- 학습 노트 추가 (있으면)
|
|
288
|
+
- \`lastRunAt\` 도 같이 현재 시각으로 갱신
|
|
289
|
+
|
|
290
|
+
## ⚠ state.json watchlist[] 항목의 정확한 필드명 (UI 가 이 명명으로 읽음)
|
|
291
|
+
|
|
292
|
+
\`\`\`json
|
|
293
|
+
{
|
|
294
|
+
"code": "036010", // 종목 코드 6자리 (필수)
|
|
295
|
+
"name": "쓰리에이로직스", // 종목명 (필수)
|
|
296
|
+
"pattern": "yinYangBull", // 매칭 패턴 또는 자연어 (필수)
|
|
297
|
+
"reason": "D-1 +12% 거래량 5배 ...", // 진입 근거 (필수 · 자연어)
|
|
298
|
+
"addedAt": "2026-05-04T12:00:00Z", // 관심 등록 시각 ISO (필수)
|
|
299
|
+
"triggerPrice": 12500, // 진입 트리거 (선택)
|
|
300
|
+
"targetPrice": 14000, // 목표가 (선택)
|
|
301
|
+
"stopPrice": 11800, // 손절가 (선택)
|
|
302
|
+
"expiresAt": "2026-05-09T00:00:00Z" // 관심 만료 (선택)
|
|
303
|
+
}
|
|
304
|
+
\`\`\`
|
|
305
|
+
|
|
306
|
+
추가 메타 (score · stars · watcherIds 등) 는 자유 필드로 보존. 다만 위 9개 키는 정확히 사용.
|
|
307
|
+
**symbolCode/symbolName/note/entryTriggerKrw 같은 별칭 사용 금지** (UI 표시 안 됨).
|
|
308
|
+
|
|
309
|
+
# 8. 마지막 응답
|
|
310
|
+
|
|
311
|
+
수행한 작업을 한 단락으로 요약 (사용자에게 보일 텍스트). 예:
|
|
312
|
+
- "watchlist 의 한화솔루션 트리거 도달 안 함 (현재가 51,000원 vs 트리거 48,650원). 매수 보류. 신규 발굴 결과 동아엘텍 (088130) 패턴3 후보로 watchlist 추가. 다음 사이클은 내일 09:30 KST 예약."
|
|
313
|
+
|
|
314
|
+
이제 시작하세요.`;
|
|
315
|
+
}
|
|
316
|
+
/* ================================================================
|
|
317
|
+
claude-cli subprocess
|
|
318
|
+
================================================================ */
|
|
319
|
+
async function runClaude(binary, args, timeoutSec) {
|
|
320
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !k.startsWith("CLAUDE")));
|
|
321
|
+
return new Promise((resolve, reject) => {
|
|
322
|
+
const child = spawn(binary, args, {
|
|
323
|
+
env,
|
|
324
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
325
|
+
});
|
|
326
|
+
let stdout = "";
|
|
327
|
+
let stderr = "";
|
|
328
|
+
let killed = false;
|
|
329
|
+
const timer = setTimeout(() => {
|
|
330
|
+
killed = true;
|
|
331
|
+
try {
|
|
332
|
+
child.kill("SIGTERM");
|
|
333
|
+
}
|
|
334
|
+
catch { /* ignore */ }
|
|
335
|
+
}, timeoutSec * 1000);
|
|
336
|
+
child.stdout.on("data", (d) => { stdout += d.toString("utf-8"); });
|
|
337
|
+
child.stderr.on("data", (d) => { stderr += d.toString("utf-8"); });
|
|
338
|
+
child.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
339
|
+
child.on("close", (code) => {
|
|
340
|
+
clearTimeout(timer);
|
|
341
|
+
if (killed)
|
|
342
|
+
return reject(new Error(`claude-cli 타임아웃 (${timeoutSec}s)`));
|
|
343
|
+
if (code !== 0 && code !== null) {
|
|
344
|
+
return reject(new Error(`claude-cli exit ${code} · stderr: ${stderr.slice(0, 500)}`));
|
|
345
|
+
}
|
|
346
|
+
resolve({ stdout, stderr });
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function extractAssistantText(stdout) {
|
|
351
|
+
// claude-cli json output: 단일 객체 또는 NDJSON. 마지막 assistant text 추출.
|
|
352
|
+
const trimmed = stdout.trim();
|
|
353
|
+
if (!trimmed)
|
|
354
|
+
return "";
|
|
355
|
+
// 단일 객체
|
|
356
|
+
try {
|
|
357
|
+
const obj = JSON.parse(trimmed);
|
|
358
|
+
if (obj.result)
|
|
359
|
+
return String(obj.result);
|
|
360
|
+
if (obj.text)
|
|
361
|
+
return String(obj.text);
|
|
362
|
+
if (Array.isArray(obj.messages)) {
|
|
363
|
+
const last = [...obj.messages].reverse().find((m) => m.role === "assistant");
|
|
364
|
+
if (last && typeof last.content === "string")
|
|
365
|
+
return last.content;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
catch { /* fallthrough */ }
|
|
369
|
+
// NDJSON — 마지막 assistant 라인
|
|
370
|
+
const lines = trimmed.split("\n").filter(Boolean);
|
|
371
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
372
|
+
try {
|
|
373
|
+
const o = JSON.parse(lines[i]);
|
|
374
|
+
if (o.role === "assistant" && typeof o.content === "string")
|
|
375
|
+
return o.content;
|
|
376
|
+
if (o.type === "text" && typeof o.text === "string")
|
|
377
|
+
return o.text;
|
|
378
|
+
}
|
|
379
|
+
catch { /* skip */ }
|
|
380
|
+
}
|
|
381
|
+
return trimmed.slice(0, 2000);
|
|
382
|
+
}
|
|
383
|
+
// 부수: os 의 path 가져오기 (linter 만족용)
|
|
384
|
+
void os;
|
|
385
|
+
void path;
|
|
386
|
+
//# sourceMappingURL=persona-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persona-agent.js","sourceRoot":"","sources":["../../../src-server/personas/persona-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPEC-27 · Stateful Persona Agent — state.json read/write 모듈.
|
|
3
|
+
*
|
|
4
|
+
* 페르소나별 ~/.mulsok-traders/personas/<slug>/state.json 을 zod 검증으로 read/write.
|
|
5
|
+
* AI agent 가 직접 도구로 read/write 하지만, 시스템 (watchdog 등) 도 동일 모듈로 read 한다.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
const ROOT = path.join(os.homedir(), ".mulsok-traders", "personas");
|
|
12
|
+
// AI 가 자유 필드명 사용해도 정합되도록 preprocess 로 normalize.
|
|
13
|
+
// 별칭 매핑: symbolCode→code, symbolName→name, note→reason,
|
|
14
|
+
// entryTriggerKrw→triggerPrice, stopLossKrw→stopPrice
|
|
15
|
+
export const WatchlistItemSchema = z.preprocess((v) => {
|
|
16
|
+
if (!v || typeof v !== "object")
|
|
17
|
+
return v;
|
|
18
|
+
const o = v;
|
|
19
|
+
return {
|
|
20
|
+
code: o.code ?? o.symbolCode ?? o.symbol_code ?? o.symbolcode,
|
|
21
|
+
name: o.name ?? o.symbolName ?? o.symbol_name ?? o.symbolname,
|
|
22
|
+
pattern: o.pattern,
|
|
23
|
+
triggerPrice: o.triggerPrice ?? o.entryTriggerKrw ?? o.entryTrigger ?? o.entryPrice,
|
|
24
|
+
targetPrice: o.targetPrice ?? o.targetPriceKrw ?? o.takeProfitKrw,
|
|
25
|
+
stopPrice: o.stopPrice ?? o.stopLossKrw ?? o.stopLoss ?? o.stopPriceKrw,
|
|
26
|
+
reason: o.reason ?? o.note ?? o.notes ?? o.rationale ?? o.summary ?? "",
|
|
27
|
+
addedAt: o.addedAt ?? o.createdAt ?? o.added_at ?? new Date().toISOString(),
|
|
28
|
+
expiresAt: o.expiresAt ?? o.expires_at,
|
|
29
|
+
};
|
|
30
|
+
}, z.object({
|
|
31
|
+
code: z.string().min(1),
|
|
32
|
+
name: z.string().min(1),
|
|
33
|
+
pattern: z.string().min(1),
|
|
34
|
+
triggerPrice: z.number().nonnegative().optional(),
|
|
35
|
+
targetPrice: z.number().nonnegative().optional(),
|
|
36
|
+
stopPrice: z.number().nonnegative().optional(),
|
|
37
|
+
reason: z.string(), // empty 허용 (AI 가 빠뜨릴 수 있음)
|
|
38
|
+
addedAt: z.string().min(1),
|
|
39
|
+
expiresAt: z.string().optional(),
|
|
40
|
+
}).passthrough()); // 추가 필드 (score, stars, watcherIds 등) 보존
|
|
41
|
+
export const PositionSchema = z.object({
|
|
42
|
+
code: z.string().min(1),
|
|
43
|
+
name: z.string().min(1),
|
|
44
|
+
quantity: z.number().int().nonnegative(),
|
|
45
|
+
avgPriceKrw: z.number().nonnegative(),
|
|
46
|
+
boughtAt: z.string().min(1),
|
|
47
|
+
rationale: z.string().default(""),
|
|
48
|
+
});
|
|
49
|
+
// passthrough() — AI 가 자유 필드 (activeWatchers · notes 등) 추가해도 보존.
|
|
50
|
+
// 우리가 강제하는 핵심 필드는 명시 + 검증, 그 외는 통과.
|
|
51
|
+
export const PersonaStateSchema = z.object({
|
|
52
|
+
version: z.literal(1).default(1),
|
|
53
|
+
slug: z.string().min(1),
|
|
54
|
+
nextRunAt: z.string().nullable().default(null),
|
|
55
|
+
lastRunAt: z.string().default(() => new Date().toISOString()),
|
|
56
|
+
watchlist: z.array(WatchlistItemSchema).default([]),
|
|
57
|
+
positions: z.array(PositionSchema).default([]),
|
|
58
|
+
stats: z.object({
|
|
59
|
+
totalTrades: z.number().int().nonnegative().default(0),
|
|
60
|
+
winCount: z.number().int().nonnegative().default(0),
|
|
61
|
+
lossCount: z.number().int().nonnegative().default(0),
|
|
62
|
+
realizedPnlKrw: z.number().default(0),
|
|
63
|
+
}).default({ totalTrades: 0, winCount: 0, lossCount: 0, realizedPnlKrw: 0 }),
|
|
64
|
+
memo: z.string().optional(),
|
|
65
|
+
}).passthrough();
|
|
66
|
+
export function personaDir(slug) {
|
|
67
|
+
return path.join(ROOT, slug);
|
|
68
|
+
}
|
|
69
|
+
export function statePath(slug) {
|
|
70
|
+
return path.join(personaDir(slug), "state.json");
|
|
71
|
+
}
|
|
72
|
+
export function strategyPath(slug) {
|
|
73
|
+
return path.join(personaDir(slug), "strategy.md");
|
|
74
|
+
}
|
|
75
|
+
export function logPath(slug) {
|
|
76
|
+
return path.join(personaDir(slug), "log.jsonl");
|
|
77
|
+
}
|
|
78
|
+
function ensureDir(slug) {
|
|
79
|
+
const dir = personaDir(slug);
|
|
80
|
+
if (!fs.existsSync(dir))
|
|
81
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
82
|
+
}
|
|
83
|
+
/** state.json 이 없으면 default 생성 후 반환. 잘못된 형식이면 default 로 초기화 + warn. */
|
|
84
|
+
export function loadState(slug) {
|
|
85
|
+
ensureDir(slug);
|
|
86
|
+
const p = statePath(slug);
|
|
87
|
+
if (!fs.existsSync(p)) {
|
|
88
|
+
const init = PersonaStateSchema.parse({ slug });
|
|
89
|
+
fs.writeFileSync(p, JSON.stringify(init, null, 2), { mode: 0o600 });
|
|
90
|
+
return init;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
94
|
+
const parsed = PersonaStateSchema.safeParse(JSON.parse(raw));
|
|
95
|
+
if (!parsed.success) {
|
|
96
|
+
console.warn(`[persona-state] ${slug} 잘못된 형식 · default 로 초기화:`, parsed.error.issues);
|
|
97
|
+
const init = PersonaStateSchema.parse({ slug });
|
|
98
|
+
fs.writeFileSync(p, JSON.stringify(init, null, 2), { mode: 0o600 });
|
|
99
|
+
return init;
|
|
100
|
+
}
|
|
101
|
+
return parsed.data;
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
console.warn(`[persona-state] ${slug} read 실패 · default:`, e);
|
|
105
|
+
return PersonaStateSchema.parse({ slug });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function saveState(slug, state) {
|
|
109
|
+
ensureDir(slug);
|
|
110
|
+
const validated = PersonaStateSchema.parse({ ...state, slug });
|
|
111
|
+
fs.writeFileSync(statePath(slug), JSON.stringify(validated, null, 2), { mode: 0o600 });
|
|
112
|
+
return validated;
|
|
113
|
+
}
|
|
114
|
+
export function loadStrategy(slug) {
|
|
115
|
+
ensureDir(slug);
|
|
116
|
+
const p = strategyPath(slug);
|
|
117
|
+
if (!fs.existsSync(p)) {
|
|
118
|
+
const init = defaultStrategyMd(slug);
|
|
119
|
+
fs.writeFileSync(p, init, { mode: 0o600 });
|
|
120
|
+
return init;
|
|
121
|
+
}
|
|
122
|
+
return fs.readFileSync(p, "utf-8");
|
|
123
|
+
}
|
|
124
|
+
export function saveStrategy(slug, content) {
|
|
125
|
+
ensureDir(slug);
|
|
126
|
+
fs.writeFileSync(strategyPath(slug), content, { mode: 0o600 });
|
|
127
|
+
}
|
|
128
|
+
export function appendLog(slug, event) {
|
|
129
|
+
ensureDir(slug);
|
|
130
|
+
const line = JSON.stringify({ ...event, at: new Date().toISOString() }) + "\n";
|
|
131
|
+
fs.appendFileSync(logPath(slug), line, { mode: 0o600 });
|
|
132
|
+
}
|
|
133
|
+
function defaultStrategyMd(slug) {
|
|
134
|
+
return `# ${slug} 전략 노트
|
|
135
|
+
|
|
136
|
+
## 핵심 원칙
|
|
137
|
+
(아직 작성 전. AI 가 매 사이클마다 자기 전략을 정리합니다.)
|
|
138
|
+
|
|
139
|
+
## 현재 시장 판단
|
|
140
|
+
(첫 실행 시 작성)
|
|
141
|
+
|
|
142
|
+
## 관심종목 현황
|
|
143
|
+
(state.json 의 watchlist 와 동기 · 자연어 plan)
|
|
144
|
+
|
|
145
|
+
## 패턴별 성공률 (누적)
|
|
146
|
+
(첫 거래 후 자동 갱신)
|
|
147
|
+
|
|
148
|
+
## 학습 노트
|
|
149
|
+
(첫 실행 — 아직 없음)
|
|
150
|
+
|
|
151
|
+
## 주의 종목 이력
|
|
152
|
+
(없음)
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
*마지막 업데이트: ${new Date().toISOString()}*
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
/* ================================================================
|
|
159
|
+
Path 안전 검증 — agent 도구가 페르소나 폴더 밖 접근 시도 차단
|
|
160
|
+
================================================================ */
|
|
161
|
+
/**
|
|
162
|
+
* 주어진 path 가 ~/.mulsok-traders/personas/<slug>/ 안에 있는지 검증.
|
|
163
|
+
* 도구 호출 (Read/Write/Edit) 의 path 인자에 사용.
|
|
164
|
+
*/
|
|
165
|
+
export function isPathInPersonaDir(slug, target) {
|
|
166
|
+
const base = personaDir(slug);
|
|
167
|
+
const resolved = path.resolve(target);
|
|
168
|
+
return resolved.startsWith(base + path.sep) || resolved === base;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=persona-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persona-state.js","sourceRoot":"","sources":["../../../src-server/personas/persona-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,iBAAiB,EAAE,UAAU,CAAC,CAAC;AAEpE,iDAAiD;AACjD,wDAAwD;AACxD,wDAAwD;AACxD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE;IACpD,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,CAAC,GAAG,CAA4B,CAAC;IACvC,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,UAAU;QAC7D,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,UAAU;QAC7D,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,UAAU;QACnF,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,aAAa;QACjE,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,YAAY;QACvE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE;QACvE,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC3E,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,UAAU;KACvC,CAAC;AACJ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;IACV,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACjD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IAChD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IAC9C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAoB,2BAA2B;IACjE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAuB,wCAAwC;AAGjF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACxC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE;IACrC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;CAClC,CAAC,CAAC;AAGH,iEAAiE;AACjE,oCAAoC;AACpC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC9C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7D,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACnD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC9C,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACtD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACnD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACpD,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;KACtC,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;IAC5E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC,WAAW,EAAE,CAAC;AAGjB,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,SAAS,CAAC,IAAI,CAAC,CAAC;IAChB,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,mBAAmB,IAAI,0BAA0B,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACrF,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACpE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,mBAAmB,IAAI,qBAAqB,EAAE,CAAC,CAAC,CAAC;QAC9D,OAAO,kBAAkB,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAAmB;IACzD,SAAS,CAAC,IAAI,CAAC,CAAC;IAChB,MAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACvF,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,SAAS,CAAC,IAAI,CAAC,CAAC;IAChB,MAAM,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACrC,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAe;IACxD,SAAS,CAAC,IAAI,CAAC,CAAC;IAChB,EAAE,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAA8B;IACpE,SAAS,CAAC,IAAI,CAAC,CAAC;IAChB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC;IAC/E,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;aAqBL,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;CACpC,CAAC;AACF,CAAC;AAED;;sEAEsE;AAEtE;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,MAAc;IAC7D,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,IAAI,CAAC;AACnE,CAAC"}
|