@triflux/core 10.50.1 → 10.51.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/hooks/agy-session-hook.mjs +11 -56
- package/hooks/codex-session-hook.mjs +11 -101
- package/hub/bridge.mjs +1 -79
- package/hub/cli-adapter-base.mjs +12 -90
- package/hub/codex-adapter.mjs +0 -1
- package/hub/codex-preflight.mjs +5 -28
- package/hub/index.mjs +3 -9
- package/hub/lib/cto-env.mjs +0 -23
- package/hub/lib/known-errors.json +2 -2
- package/hub/lib/process-utils.mjs +0 -123
- package/hub/lib/spawn-trace.mjs +3 -2
- package/hub/lib/timeout-defaults.mjs +0 -1
- package/hub/paths.mjs +0 -6
- package/hub/pipeline/index.mjs +0 -190
- package/hub/pipeline/transitions.mjs +0 -10
- package/hub/reflexion.mjs +1 -84
- package/hub/role-contract.mjs +0 -2
- package/hud/cli-policy.mjs +0 -26
- package/hud/colors.mjs +0 -13
- package/hud/constants.mjs +7 -153
- package/hud/context-monitor.mjs +3 -64
- package/hud/hud-qos-status.mjs +54 -264
- package/hud/providers/claude.mjs +4 -16
- package/hud/providers/gemini.mjs +83 -689
- package/hud/renderers.mjs +59 -367
- package/hud/terminal.mjs +1 -63
- package/hud/utils.mjs +2 -26
- package/package.json +1 -4
- package/scripts/lib/agent-route-policy.mjs +162 -14
- package/scripts/lib/claudemd-scanner.mjs +2 -199
- package/scripts/lib/cli-claude.mjs +0 -2
- package/scripts/lib/context.mjs +3 -19
- package/scripts/lib/env-probe.mjs +5 -64
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +25 -104
- package/scripts/lib/session-presence.mjs +1 -1
- package/scripts/lib/skill-template.mjs +0 -271
- package/hub/codex-compat.mjs +0 -12
- package/hub/dynamic-routing-engine.mjs +0 -511
- package/hub/fullcycle.mjs +0 -113
- package/hub/gemini-adapter.mjs +0 -174
- package/hub/intent.mjs +0 -321
- package/hub/lib/mcp-response-cache.mjs +0 -239
- package/hub/lib/path-utils.mjs +0 -165
- package/hub/lib/risk-tier.mjs +0 -53
- package/hub/lib/ssh-command.mjs +0 -231
- package/hub/lib/staged-file-detect.mjs +0 -43
- package/hub/lib/tfx-route-args.mjs +0 -353
- package/hub/lib/timeline-adapter.mjs +0 -190
- package/hub/pipeline/gates/confidence.mjs +0 -75
- package/hub/pipeline/gates/consensus.mjs +0 -106
- package/hub/pipeline/gates/index.mjs +0 -9
- package/hub/pipeline/gates/selfcheck.mjs +0 -120
- package/hub/quality/deslop.mjs +0 -274
- package/hub/research.mjs +0 -155
- package/hub/role-control-events.mjs +0 -113
- package/hub/routing/complexity.mjs +0 -269
- package/hub/routing/index.mjs +0 -122
- package/hub/routing/q-learning.mjs +0 -387
- package/hub/routing-snapshot.mjs +0 -261
- package/hub/token-mode.mjs +0 -228
- package/hud/mission-board.mjs +0 -56
- package/hud/providers/cto.mjs +0 -76
- package/mesh/index.mjs +0 -69
- package/mesh/mesh-budget.mjs +0 -137
- package/mesh/mesh-heartbeat.mjs +0 -100
- package/mesh/mesh-protocol.mjs +0 -96
- package/mesh/mesh-queue.mjs +0 -163
- package/mesh/mesh-registry.mjs +0 -78
- package/mesh/mesh-router.mjs +0 -77
- package/scripts/lib/agent-json.mjs +0 -27
- package/scripts/lib/async.mjs +0 -174
- package/scripts/lib/cli-agy.mjs +0 -85
- package/scripts/lib/cli-codex.mjs +0 -62
- package/scripts/lib/cli-gemini.mjs +0 -37
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
- package/scripts/lib/env.mjs +0 -14
- package/scripts/lib/hub.mjs +0 -48
- package/scripts/lib/pid.mjs +0 -63
- package/scripts/lib/quota.mjs +0 -47
- package/scripts/lib/skill-state.mjs +0 -220
- package/scripts/lib/team.mjs +0 -75
- package/scripts/lib/timeout.mjs +0 -66
- package/scripts/lib/tmp.mjs +0 -26
- package/scripts/lib/toml.mjs +0 -132
package/hub/token-mode.mjs
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
// hub/token-mode.mjs — Token Efficiency Mode
|
|
2
|
-
// GAP 분석 P2 #7: 심볼 통신 + 약어로 30-50% 토큰 절감
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @experimental 런타임 미연결 — 향후 통합 예정
|
|
6
|
-
* @experimental
|
|
7
|
-
* @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>}
|
|
8
|
-
*/
|
|
9
|
-
export const COMPACT_RULES = [
|
|
10
|
-
// ── 심볼 치환 (긴 매칭 우선 정렬) ──
|
|
11
|
-
{ from: ["greater than or equal"], to: "≥", type: "symbol" },
|
|
12
|
-
{ from: ["less than or equal"], to: "≤", type: "symbol" },
|
|
13
|
-
{ from: ["results in", "결과적으로"], to: "→", type: "symbol" },
|
|
14
|
-
{ from: ["therefore", "따라서"], to: "∴", type: "symbol" },
|
|
15
|
-
{ from: ["because", "왜냐하면"], to: "∵", type: "symbol" },
|
|
16
|
-
{ from: ["approximately", "대략"], to: "≈", type: "symbol" },
|
|
17
|
-
{ from: ["not equal", "같지 않"], to: "≠", type: "symbol" },
|
|
18
|
-
{ from: ["in progress", "진행 중"], to: "⏳", type: "symbol" },
|
|
19
|
-
{ from: ["completed", "완료"], to: "✓", type: "symbol" },
|
|
20
|
-
{ from: ["success", "성공"], to: "✓", type: "symbol" },
|
|
21
|
-
{ from: ["failure", "실패"], to: "✗", type: "symbol" },
|
|
22
|
-
{ from: ["warning", "경고"], to: "⚠", type: "symbol" },
|
|
23
|
-
{ from: ["error", "에러"], to: "✗", type: "symbol" },
|
|
24
|
-
{ from: ["pending", "대기"], to: "⏸", type: "symbol" },
|
|
25
|
-
|
|
26
|
-
// ── 약어 (긴 매칭 우선 정렬) ──
|
|
27
|
-
{ from: ["configuration", "설정"], to: "cfg", type: "abbrev" },
|
|
28
|
-
{ from: ["implementation", "구현"], to: "impl", type: "abbrev" },
|
|
29
|
-
{ from: ["architecture", "아키텍처"], to: "arch", type: "abbrev" },
|
|
30
|
-
{ from: ["dependency", "의존성"], to: "dep", type: "abbrev" },
|
|
31
|
-
{ from: ["function", "함수"], to: "fn", type: "abbrev" },
|
|
32
|
-
{ from: ["parameter", "파라미터"], to: "param", type: "abbrev" },
|
|
33
|
-
{ from: ["repository", "저장소"], to: "repo", type: "abbrev" },
|
|
34
|
-
{ from: ["environment", "환경"], to: "env", type: "abbrev" },
|
|
35
|
-
{ from: ["variable", "변수"], to: "var", type: "abbrev" },
|
|
36
|
-
{ from: ["directory", "디렉토리"], to: "dir", type: "abbrev" },
|
|
37
|
-
|
|
38
|
-
// ── 한국어 동사/명령형 약어 ──
|
|
39
|
-
{ from: ["구현해"], to: "impl", type: "abbrev" },
|
|
40
|
-
{ from: ["확인해"], to: "check", type: "abbrev" },
|
|
41
|
-
{ from: ["수정해"], to: "fix", type: "abbrev" },
|
|
42
|
-
{ from: ["테스트"], to: "test", type: "abbrev" },
|
|
43
|
-
{ from: ["리뷰"], to: "review", type: "abbrev" },
|
|
44
|
-
{ from: ["분석"], to: "analyze", type: "abbrev" },
|
|
45
|
-
{ from: ["설계"], to: "design", type: "abbrev" },
|
|
46
|
-
{ from: ["문서화"], to: "docs", type: "abbrev" },
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
50
|
-
export const REVIEW_RULES = [
|
|
51
|
-
{ from: ["looks good to me", "lgtm"], to: "✓lgtm", type: "abbrev" },
|
|
52
|
-
{ from: ["needs changes", "수정 필요"], to: "✗chg", type: "abbrev" },
|
|
53
|
-
{ from: ["nitpick", "사소한"], to: "nit", type: "abbrev" },
|
|
54
|
-
{ from: ["blocking", "블로킹"], to: "blk", type: "abbrev" },
|
|
55
|
-
{ from: ["suggestion", "제안"], to: "sug", type: "abbrev" },
|
|
56
|
-
{ from: ["question", "질문"], to: "q", type: "abbrev" },
|
|
57
|
-
{ from: ["approved", "승인"], to: "✓apv", type: "abbrev" },
|
|
58
|
-
{ from: ["request changes", "변경 요청"], to: "✗req", type: "abbrev" },
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
62
|
-
export const DESIGN_RULES = [
|
|
63
|
-
{ from: ["component", "컴포넌트"], to: "cmp", type: "abbrev" },
|
|
64
|
-
{ from: ["interface", "인터페이스"], to: "iface", type: "abbrev" },
|
|
65
|
-
{ from: ["abstraction", "추상화"], to: "abs", type: "abbrev" },
|
|
66
|
-
{ from: ["pattern", "패턴"], to: "ptn", type: "abbrev" },
|
|
67
|
-
{ from: ["dependency injection", "의존성 주입"], to: "di", type: "abbrev" },
|
|
68
|
-
{ from: ["single responsibility", "단일 책임"], to: "srp", type: "abbrev" },
|
|
69
|
-
{ from: ["open closed", "개방 폐쇄"], to: "ocp", type: "abbrev" },
|
|
70
|
-
{ from: ["inheritance", "상속"], to: "inh", type: "abbrev" },
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
74
|
-
export const DOCS_RULES = [
|
|
75
|
-
{ from: ["description", "설명"], to: "desc", type: "abbrev" },
|
|
76
|
-
{ from: ["example", "예시"], to: "ex", type: "abbrev" },
|
|
77
|
-
{ from: ["reference", "참조"], to: "ref", type: "abbrev" },
|
|
78
|
-
{ from: ["introduction", "소개"], to: "intro", type: "abbrev" },
|
|
79
|
-
{ from: ["deprecated", "사용 중단"], to: "dep", type: "abbrev" },
|
|
80
|
-
{ from: ["optional", "선택적"], to: "opt", type: "abbrev" },
|
|
81
|
-
{ from: ["required", "필수"], to: "req", type: "abbrev" },
|
|
82
|
-
{ from: ["returns", "반환"], to: "ret", type: "abbrev" },
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
// ── 프로필 맵 ──
|
|
86
|
-
|
|
87
|
-
/** @type {Record<string, Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>>} */
|
|
88
|
-
const PROFILE_MAP = {
|
|
89
|
-
default: COMPACT_RULES,
|
|
90
|
-
review: [...COMPACT_RULES, ...REVIEW_RULES],
|
|
91
|
-
design: [...COMPACT_RULES, ...DESIGN_RULES],
|
|
92
|
-
docs: [...COMPACT_RULES, ...DOCS_RULES],
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// ── 내부: 정렬된 치환 쌍 빌드 헬퍼 ──
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 규칙 배열로부터 패턴 쌍을 빌드하고 긴 패턴 우선 정렬
|
|
99
|
-
* @param {Array<{ from: string[], to: string }>} rules
|
|
100
|
-
* @returns {Array<{ pattern: string, to: string, len: number }>}
|
|
101
|
-
*/
|
|
102
|
-
function buildCompactPairs(rules) {
|
|
103
|
-
const pairs = [];
|
|
104
|
-
for (const rule of rules) {
|
|
105
|
-
for (const keyword of rule.from) {
|
|
106
|
-
pairs.push({ pattern: keyword, to: rule.to, len: keyword.length });
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
pairs.sort((a, b) => b.len - a.len);
|
|
110
|
-
return pairs;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 규칙 배열로부터 확장 쌍을 빌드
|
|
115
|
-
* @param {Array<{ from: string[], to: string }>} rules
|
|
116
|
-
* @returns {Array<{ symbol: string, restore: string }>}
|
|
117
|
-
*/
|
|
118
|
-
function buildExpandPairs(rules) {
|
|
119
|
-
return rules.map((rule) => ({ symbol: rule.to, restore: rule.from[0] }));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 기본 프로필 쌍 (기존 동작 유지)
|
|
123
|
-
const _compactPairs = buildCompactPairs(COMPACT_RULES);
|
|
124
|
-
const _expandPairs = buildExpandPairs(COMPACT_RULES);
|
|
125
|
-
|
|
126
|
-
// ── 코드 블록 보호 유틸 ──
|
|
127
|
-
|
|
128
|
-
const CODE_BLOCK_RE = /```[\s\S]*?```/g;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 코드 블록을 플레이스홀더로 치환, 변환 후 복원
|
|
132
|
-
* @param {string} text
|
|
133
|
-
* @param {(segment: string) => string} transform
|
|
134
|
-
* @returns {string}
|
|
135
|
-
*/
|
|
136
|
-
function withCodeProtection(text, transform) {
|
|
137
|
-
const blocks = [];
|
|
138
|
-
const placeholder = "\x00CB";
|
|
139
|
-
let idx = 0;
|
|
140
|
-
const masked = text.replace(CODE_BLOCK_RE, (match) => {
|
|
141
|
-
blocks.push(match);
|
|
142
|
-
return `${placeholder}${idx++}${placeholder}`;
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const transformed = transform(masked);
|
|
146
|
-
|
|
147
|
-
// 플레이스홀더 복원
|
|
148
|
-
return transformed.replace(
|
|
149
|
-
new RegExp(`${placeholder}(\\d+)${placeholder}`, "g"),
|
|
150
|
-
(_, i) => blocks[Number(i)],
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ── compact 모드 상태 ──
|
|
155
|
-
|
|
156
|
-
let _compactMode = false;
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* compact 모드 활성 여부
|
|
160
|
-
* @returns {boolean}
|
|
161
|
-
*/
|
|
162
|
-
export function isCompactMode() {
|
|
163
|
-
return _compactMode;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* 텍스트를 compact 모드로 변환
|
|
168
|
-
* 심볼 치환 + 약어 적용, 코드 블록 내부는 보호
|
|
169
|
-
* @param {string} text
|
|
170
|
-
* @returns {string}
|
|
171
|
-
*/
|
|
172
|
-
export function compactify(text) {
|
|
173
|
-
if (!text || typeof text !== "string") return text ?? "";
|
|
174
|
-
_compactMode = true;
|
|
175
|
-
|
|
176
|
-
return withCodeProtection(text, (segment) => {
|
|
177
|
-
let result = segment;
|
|
178
|
-
for (const { pattern, to } of _compactPairs) {
|
|
179
|
-
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
180
|
-
const re = new RegExp(escaped, "gi");
|
|
181
|
-
result = result.replace(re, to);
|
|
182
|
-
}
|
|
183
|
-
return result;
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* 텍스트를 compact 모드로 변환 (도메인 프로필 선택 가능)
|
|
189
|
-
* @param {string} text
|
|
190
|
-
* @param {'default'|'review'|'design'|'docs'} [profile='default'] — 도메인 프로필
|
|
191
|
-
* @returns {string}
|
|
192
|
-
*/
|
|
193
|
-
export function applyCompactRules(text, profile = "default") {
|
|
194
|
-
if (!text || typeof text !== "string") return text ?? "";
|
|
195
|
-
|
|
196
|
-
const rules = PROFILE_MAP[profile] ?? COMPACT_RULES;
|
|
197
|
-
const pairs = buildCompactPairs(rules);
|
|
198
|
-
|
|
199
|
-
return withCodeProtection(text, (segment) => {
|
|
200
|
-
let result = segment;
|
|
201
|
-
for (const { pattern, to } of pairs) {
|
|
202
|
-
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
203
|
-
const re = new RegExp(escaped, "gi");
|
|
204
|
-
result = result.replace(re, to);
|
|
205
|
-
}
|
|
206
|
-
return result;
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* compact 텍스트를 원래 형태로 복원 (best-effort)
|
|
212
|
-
* @param {string} text
|
|
213
|
-
* @returns {string}
|
|
214
|
-
*/
|
|
215
|
-
export function expand(text) {
|
|
216
|
-
if (!text || typeof text !== "string") return text ?? "";
|
|
217
|
-
_compactMode = false;
|
|
218
|
-
|
|
219
|
-
return withCodeProtection(text, (segment) => {
|
|
220
|
-
let result = segment;
|
|
221
|
-
for (const { symbol, restore } of _expandPairs) {
|
|
222
|
-
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
223
|
-
const re = new RegExp(escaped, "g");
|
|
224
|
-
result = result.replace(re, restore);
|
|
225
|
-
}
|
|
226
|
-
return result;
|
|
227
|
-
});
|
|
228
|
-
}
|
package/hud/mission-board.mjs
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// HUD Mission Board — .omc/state/sessions/ 기반 에이전트 진행률 집계
|
|
3
|
-
// ============================================================================
|
|
4
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
5
|
-
import { homedir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
|
|
8
|
-
const SESSIONS_DIR = join(homedir(), ".omc", "state", "sessions");
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* .omc/state/sessions/ 디렉토리를 읽어 팀 상태를 반환한다.
|
|
12
|
-
* @returns {{ agents: Array<{name: string, status: string, progress: number}>, dagLevel: number, totalProgress: number } | null}
|
|
13
|
-
*/
|
|
14
|
-
export async function getMissionBoardState(sessionsDir = SESSIONS_DIR) {
|
|
15
|
-
let entries;
|
|
16
|
-
try {
|
|
17
|
-
entries = await readdir(sessionsDir);
|
|
18
|
-
} catch {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const jsonFiles = entries.filter((f) => f.endsWith(".json"));
|
|
23
|
-
if (jsonFiles.length === 0) return null;
|
|
24
|
-
|
|
25
|
-
const agents = [];
|
|
26
|
-
for (const file of jsonFiles) {
|
|
27
|
-
let data;
|
|
28
|
-
try {
|
|
29
|
-
const raw = await readFile(join(sessionsDir, file), "utf8");
|
|
30
|
-
data = JSON.parse(raw);
|
|
31
|
-
} catch {
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const name = data.name ?? file.replace(/\.json$/, "");
|
|
36
|
-
const status = data.status ?? "idle";
|
|
37
|
-
const progress = typeof data.progress === "number" ? data.progress : 0;
|
|
38
|
-
agents.push({ name, status, progress });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (agents.length === 0) return null;
|
|
42
|
-
|
|
43
|
-
const totalProgress =
|
|
44
|
-
agents.length > 0
|
|
45
|
-
? Math.round(
|
|
46
|
-
agents.reduce((sum, a) => sum + a.progress, 0) / agents.length,
|
|
47
|
-
)
|
|
48
|
-
: 0;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
agents,
|
|
52
|
-
// TODO: derive dagLevel from real mission dependency metadata instead of hardcoding 0.
|
|
53
|
-
dagLevel: 0,
|
|
54
|
-
totalProgress,
|
|
55
|
-
};
|
|
56
|
-
}
|
package/hud/providers/cto.mjs
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { resolveRoleControlSnapshot } from "../../hub/lib/cto-env.mjs";
|
|
4
|
-
import {
|
|
5
|
-
CTO_LAKE_CURRENT_RELATIVE_PATH,
|
|
6
|
-
CTO_STATUS_LINE_MAX_CHARS,
|
|
7
|
-
CTO_STATUS_STALE_MS,
|
|
8
|
-
} from "../constants.mjs";
|
|
9
|
-
|
|
10
|
-
function oneLine(value) {
|
|
11
|
-
return String(value || "")
|
|
12
|
-
.replace(/\x1b\[[0-9;]*m/g, "")
|
|
13
|
-
.replace(/\s+/g, " ")
|
|
14
|
-
.trim();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function capLine(line) {
|
|
18
|
-
const text = oneLine(line);
|
|
19
|
-
if (text.length <= CTO_STATUS_LINE_MAX_CHARS) return text;
|
|
20
|
-
return `${text.slice(0, CTO_STATUS_LINE_MAX_CHARS - 3)}...`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function resolveCurrentPath({ currentPath, rootDir } = {}) {
|
|
24
|
-
if (currentPath) return currentPath;
|
|
25
|
-
return join(rootDir || process.cwd(), CTO_LAKE_CURRENT_RELATIVE_PATH);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function extractBriefVersion(lines) {
|
|
29
|
-
const versionLine = lines.find((line) => /^brief_version:\s*/i.test(line));
|
|
30
|
-
return oneLine(versionLine?.replace(/^brief_version:\s*/i, "")) || null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function extractStatusLine(lines) {
|
|
34
|
-
const summaryLine = lines.find((line) => /^summary:\s*/i.test(line));
|
|
35
|
-
const summary = oneLine(summaryLine?.replace(/^summary:\s*/i, ""));
|
|
36
|
-
if (summary) return capLine(summary);
|
|
37
|
-
|
|
38
|
-
const sectionHeaders = new Set([
|
|
39
|
-
"repo_state",
|
|
40
|
-
"active_goals",
|
|
41
|
-
"swarm_shards",
|
|
42
|
-
"hub_status",
|
|
43
|
-
"recent_events",
|
|
44
|
-
"generated_at",
|
|
45
|
-
"injection_note",
|
|
46
|
-
]);
|
|
47
|
-
const fallback = lines.find((line) => {
|
|
48
|
-
const clean = oneLine(line);
|
|
49
|
-
return (
|
|
50
|
-
clean && !/^brief_version:\s*/i.test(clean) && !sectionHeaders.has(clean)
|
|
51
|
-
);
|
|
52
|
-
});
|
|
53
|
-
return fallback ? capLine(fallback) : null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function readCtoStatus(options = {}) {
|
|
57
|
-
if (!resolveRoleControlSnapshot(options.env).auto_collect_enabled)
|
|
58
|
-
return null;
|
|
59
|
-
try {
|
|
60
|
-
const currentPath = resolveCurrentPath(options);
|
|
61
|
-
const stat = statSync(currentPath);
|
|
62
|
-
const raw = readFileSync(currentPath, "utf8");
|
|
63
|
-
const lines = raw.split(/\r?\n/u).map(oneLine).filter(Boolean);
|
|
64
|
-
const line = extractStatusLine(lines);
|
|
65
|
-
if (!line) return null;
|
|
66
|
-
|
|
67
|
-
const version = extractBriefVersion(lines);
|
|
68
|
-
const isStale = Date.now() - stat.mtimeMs > CTO_STATUS_STALE_MS;
|
|
69
|
-
return {
|
|
70
|
-
line,
|
|
71
|
-
rightTag: isStale ? "stale" : version || "cto",
|
|
72
|
-
};
|
|
73
|
-
} catch {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
}
|
package/mesh/index.mjs
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { readdirSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
export { createMeshBudget } from "./mesh-budget.mjs";
|
|
5
|
-
export { createHeartbeatMonitor } from "./mesh-heartbeat.mjs";
|
|
6
|
-
export {
|
|
7
|
-
createMessage,
|
|
8
|
-
deserialize,
|
|
9
|
-
MSG_TYPES,
|
|
10
|
-
serialize,
|
|
11
|
-
validate,
|
|
12
|
-
} from "./mesh-protocol.mjs";
|
|
13
|
-
export { createMessageQueue } from "./mesh-queue.mjs";
|
|
14
|
-
export { createRegistry } from "./mesh-registry.mjs";
|
|
15
|
-
export { routeMessage, routeOrDeadLetter } from "./mesh-router.mjs";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Loads skills assigned to a specific agent from a skills directory.
|
|
19
|
-
* Reuses the same directory-scan approach as generateSkillDocs().
|
|
20
|
-
*
|
|
21
|
-
* @param {string} agentId - The agent identifier
|
|
22
|
-
* @param {string} skillsDir - Path to the skills directory
|
|
23
|
-
* @returns {Promise<string[]>} Array of skill names available to this agent
|
|
24
|
-
*/
|
|
25
|
-
export async function loadSkillsForAgent(agentId, skillsDir) {
|
|
26
|
-
if (!agentId || typeof agentId !== "string") {
|
|
27
|
-
throw new TypeError("agentId must be a non-empty string");
|
|
28
|
-
}
|
|
29
|
-
if (!skillsDir || typeof skillsDir !== "string") {
|
|
30
|
-
throw new TypeError("skillsDir must be a non-empty string");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let entries;
|
|
34
|
-
try {
|
|
35
|
-
entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
36
|
-
} catch {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const skills = [];
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
if (!entry.isDirectory()) continue;
|
|
43
|
-
const skillName = entry.name;
|
|
44
|
-
const skillPath = join(skillsDir, skillName, "SKILL.md");
|
|
45
|
-
let skillContent = null;
|
|
46
|
-
try {
|
|
47
|
-
const { readFileSync } = await import("node:fs");
|
|
48
|
-
skillContent = readFileSync(skillPath, "utf8");
|
|
49
|
-
} catch {
|
|
50
|
-
// Skill has no SKILL.md — include it anyway
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// If SKILL.md mentions the agentId or no agent restriction, include it
|
|
54
|
-
const isRestricted = skillContent
|
|
55
|
-
? /^agents?\s*:/im.test(skillContent)
|
|
56
|
-
: false;
|
|
57
|
-
|
|
58
|
-
if (!isRestricted) {
|
|
59
|
-
skills.push(skillName);
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (skillContent?.includes(agentId)) {
|
|
64
|
-
skills.push(skillName);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return skills;
|
|
69
|
-
}
|
package/mesh/mesh-budget.mjs
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Warning level thresholds mirroring context-monitor.mjs classifyContextThreshold().
|
|
3
|
-
* Reproduced here to avoid cross-module dependency.
|
|
4
|
-
*/
|
|
5
|
-
const WARNING_LEVELS = Object.freeze({
|
|
6
|
-
critical: 90,
|
|
7
|
-
warn: 80,
|
|
8
|
-
info: 60,
|
|
9
|
-
ok: 0,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Clamps a percentage value to [0, 100].
|
|
14
|
-
* @param {number} value
|
|
15
|
-
* @returns {number}
|
|
16
|
-
*/
|
|
17
|
-
function clampPercent(value) {
|
|
18
|
-
const n = Number(value);
|
|
19
|
-
if (!Number.isFinite(n)) return 0;
|
|
20
|
-
return Math.min(100, Math.max(0, n));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Classifies a usage percentage into a warning level.
|
|
25
|
-
* Mirrors context-monitor.mjs classifyContextThreshold().
|
|
26
|
-
* @param {number} percent
|
|
27
|
-
* @returns {{ level: string, message: string }}
|
|
28
|
-
*/
|
|
29
|
-
function classifyLevel(percent) {
|
|
30
|
-
const p = clampPercent(percent);
|
|
31
|
-
if (p >= WARNING_LEVELS.critical)
|
|
32
|
-
return { level: "critical", message: "에이전트 분할 또는 세션 교체 권장" };
|
|
33
|
-
if (p >= WARNING_LEVELS.warn) return { level: "warn", message: "압축 권장" };
|
|
34
|
-
if (p >= WARNING_LEVELS.info)
|
|
35
|
-
return { level: "info", message: "컨텍스트 절반 이상 사용" };
|
|
36
|
-
return { level: "ok", message: "" };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Creates a per-agent token budget manager.
|
|
41
|
-
* @returns {object} Budget API
|
|
42
|
-
*/
|
|
43
|
-
export function createMeshBudget() {
|
|
44
|
-
// Map<agentId, { allocated: number, consumed: number }>
|
|
45
|
-
const budgets = new Map();
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Allocates a token budget to an agent.
|
|
49
|
-
* @param {string} agentId
|
|
50
|
-
* @param {number} tokenLimit
|
|
51
|
-
*/
|
|
52
|
-
function allocate(agentId, tokenLimit) {
|
|
53
|
-
if (!agentId || typeof agentId !== "string") {
|
|
54
|
-
throw new TypeError("agentId must be a non-empty string");
|
|
55
|
-
}
|
|
56
|
-
const limit = Math.max(0, Math.round(Number(tokenLimit) || 0));
|
|
57
|
-
const existing = budgets.get(agentId);
|
|
58
|
-
budgets.set(agentId, {
|
|
59
|
-
allocated: limit,
|
|
60
|
-
consumed: existing?.consumed ?? 0,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Records token consumption for an agent.
|
|
66
|
-
* @param {string} agentId
|
|
67
|
-
* @param {number} tokens
|
|
68
|
-
* @returns {{ remaining: number, percent: number, level: string }}
|
|
69
|
-
*/
|
|
70
|
-
function consume(agentId, tokens) {
|
|
71
|
-
const budget = budgets.get(agentId);
|
|
72
|
-
if (!budget) {
|
|
73
|
-
throw new Error(`No budget allocated for agent: ${agentId}`);
|
|
74
|
-
}
|
|
75
|
-
const amount = Math.max(0, Math.round(Number(tokens) || 0));
|
|
76
|
-
const updated = {
|
|
77
|
-
allocated: budget.allocated,
|
|
78
|
-
consumed: budget.consumed + amount,
|
|
79
|
-
};
|
|
80
|
-
budgets.set(agentId, updated);
|
|
81
|
-
|
|
82
|
-
const remaining = Math.max(0, updated.allocated - updated.consumed);
|
|
83
|
-
const percent =
|
|
84
|
-
updated.allocated > 0
|
|
85
|
-
? clampPercent((updated.consumed / updated.allocated) * 100)
|
|
86
|
-
: 100;
|
|
87
|
-
const { level } = classifyLevel(percent);
|
|
88
|
-
return { remaining, percent, level };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Returns the budget status for an agent.
|
|
93
|
-
* @param {string} agentId
|
|
94
|
-
* @returns {{ allocated: number, consumed: number, remaining: number, level: string }}
|
|
95
|
-
*/
|
|
96
|
-
function getStatus(agentId) {
|
|
97
|
-
const budget = budgets.get(agentId);
|
|
98
|
-
if (!budget) {
|
|
99
|
-
return { allocated: 0, consumed: 0, remaining: 0, level: "ok" };
|
|
100
|
-
}
|
|
101
|
-
const remaining = Math.max(0, budget.allocated - budget.consumed);
|
|
102
|
-
const percent =
|
|
103
|
-
budget.allocated > 0
|
|
104
|
-
? clampPercent((budget.consumed / budget.allocated) * 100)
|
|
105
|
-
: 0;
|
|
106
|
-
const { level } = classifyLevel(percent);
|
|
107
|
-
return {
|
|
108
|
-
allocated: budget.allocated,
|
|
109
|
-
consumed: budget.consumed,
|
|
110
|
-
remaining,
|
|
111
|
-
level,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Resets consumed tokens for all agents (keeps allocations).
|
|
117
|
-
*/
|
|
118
|
-
function resetAll() {
|
|
119
|
-
for (const [agentId, budget] of budgets) {
|
|
120
|
-
budgets.set(agentId, { allocated: budget.allocated, consumed: 0 });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Returns a snapshot of all current allocations.
|
|
126
|
-
* @returns {Map<string, { allocated: number, consumed: number }>}
|
|
127
|
-
*/
|
|
128
|
-
function listAllocations() {
|
|
129
|
-
const snap = new Map();
|
|
130
|
-
for (const [id, b] of budgets) {
|
|
131
|
-
snap.set(id, Object.freeze({ ...b }));
|
|
132
|
-
}
|
|
133
|
-
return snap;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return { allocate, consume, getStatus, resetAll, listAllocations };
|
|
137
|
-
}
|
package/mesh/mesh-heartbeat.mjs
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
const DEFAULT_INTERVAL_MS = 30_000;
|
|
2
|
-
const DEFAULT_THRESHOLD_MS = 60_000;
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Creates a heartbeat monitor that tracks agent liveness.
|
|
6
|
-
*
|
|
7
|
-
* @param {object} registry - A mesh-registry instance
|
|
8
|
-
* @param {object} [opts]
|
|
9
|
-
* @param {number} [opts.intervalMs=30000] - Scan interval
|
|
10
|
-
* @param {number} [opts.thresholdMs=60000] - Stale threshold
|
|
11
|
-
* @param {function} [opts.onStale] - Called with agentId when stale detected
|
|
12
|
-
* @returns {object} HeartbeatMonitor API
|
|
13
|
-
*/
|
|
14
|
-
export function createHeartbeatMonitor(registry, opts = {}) {
|
|
15
|
-
const {
|
|
16
|
-
intervalMs = DEFAULT_INTERVAL_MS,
|
|
17
|
-
thresholdMs = DEFAULT_THRESHOLD_MS,
|
|
18
|
-
onStale,
|
|
19
|
-
} = opts;
|
|
20
|
-
|
|
21
|
-
/** @type {Map<string, number>} agentId → last heartbeat timestamp */
|
|
22
|
-
const heartbeats = new Map();
|
|
23
|
-
let timer = null;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Records a heartbeat for an agent.
|
|
27
|
-
* @param {string} agentId
|
|
28
|
-
*/
|
|
29
|
-
function recordHeartbeat(agentId) {
|
|
30
|
-
if (!agentId || typeof agentId !== "string") {
|
|
31
|
-
throw new TypeError("agentId must be a non-empty string");
|
|
32
|
-
}
|
|
33
|
-
heartbeats.set(agentId, Date.now());
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Returns agent IDs whose last heartbeat exceeds the threshold.
|
|
38
|
-
* Only considers agents currently registered in the registry.
|
|
39
|
-
*
|
|
40
|
-
* @param {number} [customThresholdMs] - Override default threshold
|
|
41
|
-
* @returns {string[]}
|
|
42
|
-
*/
|
|
43
|
-
function getStaleAgents(customThresholdMs) {
|
|
44
|
-
const threshold = customThresholdMs ?? thresholdMs;
|
|
45
|
-
const now = Date.now();
|
|
46
|
-
const registered = registry.listAll();
|
|
47
|
-
const stale = [];
|
|
48
|
-
|
|
49
|
-
for (const agent of registered) {
|
|
50
|
-
const lastBeat = heartbeats.get(agent.agentId);
|
|
51
|
-
if (lastBeat === undefined || now - lastBeat >= threshold) {
|
|
52
|
-
stale.push(agent.agentId);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return stale;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Runs a single scan: finds stale agents and invokes onStale callback.
|
|
60
|
-
*/
|
|
61
|
-
function scan() {
|
|
62
|
-
const stale = getStaleAgents();
|
|
63
|
-
if (typeof onStale === "function") {
|
|
64
|
-
for (const agentId of stale) {
|
|
65
|
-
onStale(agentId);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Starts periodic heartbeat scanning.
|
|
72
|
-
* @param {number} [customIntervalMs]
|
|
73
|
-
*/
|
|
74
|
-
function start(customIntervalMs) {
|
|
75
|
-
stop();
|
|
76
|
-
const interval = customIntervalMs ?? intervalMs;
|
|
77
|
-
timer = setInterval(scan, interval);
|
|
78
|
-
timer.unref?.();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Stops periodic scanning.
|
|
83
|
-
*/
|
|
84
|
-
function stop() {
|
|
85
|
-
if (timer !== null) {
|
|
86
|
-
clearInterval(timer);
|
|
87
|
-
timer = null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Removes heartbeat record for an agent.
|
|
93
|
-
* @param {string} agentId
|
|
94
|
-
*/
|
|
95
|
-
function remove(agentId) {
|
|
96
|
-
heartbeats.delete(agentId);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return { recordHeartbeat, getStaleAgents, start, stop, scan, remove };
|
|
100
|
-
}
|