@triflux/remote 10.41.1 → 10.42.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/cto/hygiene-actions.mjs +355 -0
- package/cto/hygiene.mjs +79 -9
- package/cto/steward.mjs +362 -0
- package/hub/log-retention.mjs +726 -0
- package/hub/pipe.mjs +2 -1
- package/hub/public/tray.html +50 -53
- package/hub/schema.sql +148 -0
- package/hub/store.mjs +15 -2
- package/hub/tools.mjs +1 -4
- package/package.json +1 -1
package/hub/pipe.mjs
CHANGED
|
@@ -222,6 +222,7 @@ export function createPipeServer({
|
|
|
222
222
|
switch (action) {
|
|
223
223
|
case "register": {
|
|
224
224
|
const result = router.registerAgent(payload);
|
|
225
|
+
if (!result?.ok) return result;
|
|
225
226
|
if (client) {
|
|
226
227
|
client.agentId = payload.agent_id;
|
|
227
228
|
client.subscriptions = new Set(
|
|
@@ -230,7 +231,7 @@ export function createPipeServer({
|
|
|
230
231
|
touchClient(client);
|
|
231
232
|
pushPendingMessages(client.agentId);
|
|
232
233
|
}
|
|
233
|
-
return { ok: true, data: { ...result, pipe_path: pipePath } };
|
|
234
|
+
return { ok: true, data: { ...result.data, pipe_path: pipePath } };
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
case "subscribe": {
|
package/hub/public/tray.html
CHANGED
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
.mini-head, .mcp-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; min-width: 0; }
|
|
63
63
|
.mini-title, .mcp-name { font-size: 12px; font-weight: 650; line-height: 1.25; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
64
64
|
.path-text { font-size: 10px; color: var(--text-muted); margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
65
|
-
.
|
|
66
|
-
.
|
|
65
|
+
.copy-btn { background: transparent; border: 1px solid rgba(10,132,255,0.45); color: #64b5ff; font-size: 10px; line-height: 1; padding: 4px 7px; cursor: pointer; border-radius: 6px; flex: 0 0 auto; }
|
|
66
|
+
.copy-btn:hover { background: var(--accent); color: #fff; }
|
|
67
67
|
.agent-view { display: flex; flex-direction: column; gap: 8px; }
|
|
68
68
|
.project-group { min-width: 0; border: 1px solid rgba(255,255,255,0.06); background: rgba(0,0,0,0.13); border-radius: 9px; overflow: hidden; }
|
|
69
69
|
.project-group > summary, .session-card > summary, .bubble-wrap > summary { list-style: none; }
|
|
@@ -115,6 +115,8 @@
|
|
|
115
115
|
.chip { display: inline-flex; align-items: center; gap: 5px; min-height: 20px; border-radius: 999px; padding: 3px 7px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.07); font-size: 10px; color: rgba(255,255,255,0.76); }
|
|
116
116
|
.copy-chip { cursor: pointer; }
|
|
117
117
|
.copy-chip:hover { border-color: rgba(10,132,255,0.42); color: #8ec9ff; }
|
|
118
|
+
.copy-chip, .copy-btn { -webkit-user-drag: none; }
|
|
119
|
+
.chat-session-id, .bubble-preview, .bubble-full, .runtime-sub, .path-text, .session-summary-title, .session-summary-sub, .project-path { -webkit-user-select: text; user-select: text; }
|
|
118
120
|
.chip.on { color: var(--success); border-color: rgba(50,215,75,0.25); background: rgba(50,215,75,0.08); }
|
|
119
121
|
.chip.partial { color: var(--warning); border-color: rgba(255,214,10,0.25); background: rgba(255,214,10,0.08); }
|
|
120
122
|
.chip.off { color: var(--danger); border-color: rgba(255,69,58,0.25); background: rgba(255,69,58,0.08); }
|
|
@@ -190,7 +192,45 @@
|
|
|
190
192
|
}
|
|
191
193
|
function renderCopyChip(label, value) {
|
|
192
194
|
if (!value) return '';
|
|
193
|
-
return `<span class="chip copy-chip" title="Copy ${escapeHtml(label)}" data-copy-text="${dataAttr(value)}">${escapeHtml(label)} ${escapeHtml(shortId(value))}</span>`;
|
|
195
|
+
return `<span class="chip copy-chip" draggable="false" title="Copy ${escapeHtml(label)}" data-copy-text="${dataAttr(value)}">${escapeHtml(label)} ${escapeHtml(shortId(value))}</span>`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function shellQuote(value) {
|
|
199
|
+
const text = String(value ?? '').trim();
|
|
200
|
+
return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function commandCwd(row) {
|
|
204
|
+
const cwd = String(row?.cwd || row?.worktreePath || '').trim();
|
|
205
|
+
if (!cwd || cwd === 'local' || cwd === 'local runtime') return '';
|
|
206
|
+
if (/^https?:\/\//iu.test(cwd)) return '';
|
|
207
|
+
return cwd;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function sessionAttachCommand(row) {
|
|
211
|
+
const sid = String(row?.sessionId || '').trim();
|
|
212
|
+
if (!sid) return '';
|
|
213
|
+
const agentId = String(row?.agent_id || row?.agent?.id || '').trim().toLowerCase();
|
|
214
|
+
const cwd = commandCwd(row);
|
|
215
|
+
const prefix = cwd ? `cd ${shellQuote(cwd)} && ` : '';
|
|
216
|
+
if (agentId === 'codex') return `${prefix}codex resume ${shellQuote(sid)}`;
|
|
217
|
+
if (agentId === 'claude') return `${prefix}claude --resume ${shellQuote(sid)}`;
|
|
218
|
+
if (agentId === 'antigravity') {
|
|
219
|
+
const conversationId = String(row?.conversationId || '').trim();
|
|
220
|
+
const token = conversationId || sid;
|
|
221
|
+
const suffix = token === sid ? '' : ` # session ${sid}`;
|
|
222
|
+
return `${prefix}agy --conversation ${shellQuote(token)}${suffix}`;
|
|
223
|
+
}
|
|
224
|
+
return `${prefix}tfx-live start --session ${shellQuote(sid)} --resume ${shellQuote(sid)}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function renderSessionCopyButton(row) {
|
|
228
|
+
const sid = String(row?.sessionId || '').trim();
|
|
229
|
+
const command = sessionAttachCommand(row);
|
|
230
|
+
if (!sid || !command) return '';
|
|
231
|
+
const pid = numericPid(row?.pid);
|
|
232
|
+
const agentId = row?.agent_id || row?.agent?.id || '';
|
|
233
|
+
return `<button class="copy-btn" draggable="false" data-copy-text="${dataAttr(command)}" data-session-id="${dataAttr(sid)}" data-pid="${dataAttr(pid)}" data-agent-id="${dataAttr(agentId)}" title="Copy launch command">Copy</button>`;
|
|
194
234
|
}
|
|
195
235
|
|
|
196
236
|
function collectOpenDetailKeys(container) {
|
|
@@ -471,10 +511,8 @@
|
|
|
471
511
|
|
|
472
512
|
function renderChatBubble(row, { side = 'worker' } = {}) {
|
|
473
513
|
const sid = row.sessionId || '';
|
|
474
|
-
const pid = numericPid(row.pid);
|
|
475
|
-
const agentId = row.agent_id || row.agent?.id || '';
|
|
476
514
|
const text = bubbleText(row, side === 'cto' ? 'CTO prompt unavailable' : 'Worker prompt unavailable');
|
|
477
|
-
const
|
|
515
|
+
const copyButton = row.synthetic ? '' : renderSessionCopyButton(row);
|
|
478
516
|
const badge = side === 'cto'
|
|
479
517
|
? '<span class="agent-badge">T</span>'
|
|
480
518
|
: renderAgentTag(row.agent) || '<span class="agent-badge">A</span>';
|
|
@@ -482,7 +520,7 @@
|
|
|
482
520
|
<div class="bubble-card ${escapeHtml(side)}">
|
|
483
521
|
<div class="chat-session-title">
|
|
484
522
|
<span style="display:flex;align-items:center;gap:6px;min-width:0;">${badge}<span class="chat-session-id">${escapeHtml(sid || side)}</span></span>
|
|
485
|
-
${
|
|
523
|
+
${copyButton}
|
|
486
524
|
</div>
|
|
487
525
|
<details class="bubble-wrap" data-open-key="bubble:${escapeHtml(side)}:${dataAttr(sid || text.slice(0, 24))}">
|
|
488
526
|
<summary class="bubble-preview">${escapeHtml(compactText(text, 'prompt'))}</summary>
|
|
@@ -494,10 +532,8 @@
|
|
|
494
532
|
|
|
495
533
|
function renderRuntimeCard(row) {
|
|
496
534
|
const sid = row.sessionId || '';
|
|
497
|
-
const pid = numericPid(row.pid);
|
|
498
|
-
const agentId = row.agent_id || row.agent?.id || '';
|
|
499
535
|
const status = row.status || row.phase || 'active';
|
|
500
|
-
const
|
|
536
|
+
const copyButton = row.focusable === false ? '' : renderSessionCopyButton(row);
|
|
501
537
|
return `
|
|
502
538
|
<div class="runtime-card">
|
|
503
539
|
${renderAgentTag(row.agent)}
|
|
@@ -505,7 +541,7 @@
|
|
|
505
541
|
<div class="runtime-title"><span class="runtime-command">${escapeHtml(commandLabel(row))}</span><span class="agent-state ${escapeHtml(status)}">${escapeHtml(status)}</span></div>
|
|
506
542
|
<div class="runtime-sub">${escapeHtml(displayPath(row.cwd || row.host || 'local runtime'))}${row.elapsed ? ` · ${escapeHtml(row.elapsed)}` : ''}</div>
|
|
507
543
|
</div>
|
|
508
|
-
${
|
|
544
|
+
${copyButton}
|
|
509
545
|
</div>
|
|
510
546
|
`;
|
|
511
547
|
}
|
|
@@ -703,54 +739,15 @@
|
|
|
703
739
|
setTimeout(() => { element.textContent = previous; }, 700);
|
|
704
740
|
}
|
|
705
741
|
}
|
|
706
|
-
function postNativeTrayMessage(message) {
|
|
707
|
-
try {
|
|
708
|
-
window?.webkit?.messageHandlers?.tray?.postMessage(message);
|
|
709
|
-
} catch (_err) {
|
|
710
|
-
// Browser fallback: the web tray can run without a native wrapper.
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
async function focusSession(sid, pid, agentId, button) {
|
|
715
|
-
if (!sid) return;
|
|
716
|
-
const previous = button?.textContent || 'Focus';
|
|
717
|
-
if (button) {
|
|
718
|
-
button.disabled = true;
|
|
719
|
-
button.textContent = 'Focusing…';
|
|
720
|
-
}
|
|
721
|
-
try {
|
|
722
|
-
const res = await fetch('/api/focus-session', {
|
|
723
|
-
method: 'POST',
|
|
724
|
-
headers: { 'Content-Type': 'application/json' },
|
|
725
|
-
body: JSON.stringify({ sessionId: sid, pid: pid || undefined, agentId: agentId || undefined })
|
|
726
|
-
});
|
|
727
|
-
const body = await res.json().catch(() => ({}));
|
|
728
|
-
if (!res.ok || body?.ok === false || body?.focus?.ok === false) throw new Error(body?.error || body?.focus?.error || 'focus failed');
|
|
729
|
-
if (button) button.textContent = 'Focused';
|
|
730
|
-
postNativeTrayMessage({ type: 'focus-complete' });
|
|
731
|
-
} catch (err) {
|
|
732
|
-
console.error(err);
|
|
733
|
-
if (button) button.textContent = 'Failed';
|
|
734
|
-
} finally {
|
|
735
|
-
setTimeout(() => {
|
|
736
|
-
if (button) {
|
|
737
|
-
button.textContent = previous;
|
|
738
|
-
button.disabled = false;
|
|
739
|
-
}
|
|
740
|
-
}, 650);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
742
|
document.addEventListener('click', event => {
|
|
744
743
|
const copyTarget = event.target.closest('[data-copy-text]');
|
|
745
744
|
if (copyTarget) {
|
|
746
745
|
copyText(copyTarget.dataset.copyText, copyTarget);
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
const focusTarget = event.target.closest('[data-focus-session]');
|
|
750
|
-
if (focusTarget) {
|
|
751
|
-
focusSession(focusTarget.dataset.sessionId, Number(focusTarget.dataset.pid || 0), focusTarget.dataset.agentId || '', focusTarget);
|
|
752
746
|
}
|
|
753
747
|
});
|
|
748
|
+
document.addEventListener('dragstart', event => {
|
|
749
|
+
if (event.target.closest('[data-copy-text]')) event.preventDefault();
|
|
750
|
+
});
|
|
754
751
|
|
|
755
752
|
setInterval(fetchState, 3000);
|
|
756
753
|
fetchState();
|
package/hub/schema.sql
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
-- tfx-hub 상태 저장소 스키마
|
|
2
|
+
-- SQLite WAL 모드 기반 메시지 버스
|
|
3
|
+
|
|
4
|
+
-- 에이전트 등록 테이블
|
|
5
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
6
|
+
agent_id TEXT PRIMARY KEY,
|
|
7
|
+
cli TEXT NOT NULL CHECK (cli IN ('codex','gemini','claude','other')),
|
|
8
|
+
pid INTEGER,
|
|
9
|
+
capabilities_json TEXT NOT NULL DEFAULT '[]',
|
|
10
|
+
topics_json TEXT NOT NULL DEFAULT '[]',
|
|
11
|
+
last_seen_ms INTEGER NOT NULL,
|
|
12
|
+
lease_expires_ms INTEGER NOT NULL,
|
|
13
|
+
status TEXT NOT NULL CHECK (status IN ('online','stale','offline')),
|
|
14
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
-- 메시지 테이블
|
|
18
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
19
|
+
id TEXT PRIMARY KEY,
|
|
20
|
+
type TEXT NOT NULL CHECK (type IN ('request','response','event','handoff','human_request','human_response','system')),
|
|
21
|
+
from_agent TEXT NOT NULL,
|
|
22
|
+
to_agent TEXT NOT NULL,
|
|
23
|
+
topic TEXT NOT NULL,
|
|
24
|
+
priority INTEGER NOT NULL CHECK (priority BETWEEN 1 AND 9),
|
|
25
|
+
ttl_ms INTEGER NOT NULL,
|
|
26
|
+
created_at_ms INTEGER NOT NULL,
|
|
27
|
+
expires_at_ms INTEGER NOT NULL,
|
|
28
|
+
correlation_id TEXT NOT NULL,
|
|
29
|
+
trace_id TEXT NOT NULL,
|
|
30
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
31
|
+
status TEXT NOT NULL CHECK (status IN ('queued','delivered','acked','expired','dead_letter'))
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
-- 메시지 수신함 (배달 추적)
|
|
35
|
+
CREATE TABLE IF NOT EXISTS message_inbox (
|
|
36
|
+
delivery_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
37
|
+
message_id TEXT NOT NULL,
|
|
38
|
+
agent_id TEXT NOT NULL,
|
|
39
|
+
delivered_at_ms INTEGER,
|
|
40
|
+
acked_at_ms INTEGER,
|
|
41
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
42
|
+
UNIQUE(message_id, agent_id),
|
|
43
|
+
FOREIGN KEY(message_id) REFERENCES messages(id) ON DELETE CASCADE
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- 사용자 입력 요청 테이블
|
|
47
|
+
CREATE TABLE IF NOT EXISTS human_requests (
|
|
48
|
+
request_id TEXT PRIMARY KEY,
|
|
49
|
+
requester_agent TEXT NOT NULL,
|
|
50
|
+
kind TEXT NOT NULL CHECK (kind IN ('captcha','approval','credential','choice','text')),
|
|
51
|
+
prompt TEXT NOT NULL,
|
|
52
|
+
schema_json TEXT NOT NULL DEFAULT '{}',
|
|
53
|
+
state TEXT NOT NULL CHECK (state IN ('pending','accepted','declined','cancelled','timed_out')),
|
|
54
|
+
deadline_ms INTEGER NOT NULL,
|
|
55
|
+
default_action TEXT NOT NULL CHECK (default_action IN ('decline','cancel','timeout_continue')),
|
|
56
|
+
correlation_id TEXT NOT NULL,
|
|
57
|
+
trace_id TEXT NOT NULL,
|
|
58
|
+
response_json TEXT
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
-- 데드 레터 큐
|
|
62
|
+
CREATE TABLE IF NOT EXISTS dead_letters (
|
|
63
|
+
message_id TEXT PRIMARY KEY,
|
|
64
|
+
reason TEXT NOT NULL,
|
|
65
|
+
failed_at_ms INTEGER NOT NULL,
|
|
66
|
+
last_error TEXT
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
-- 인덱스
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_messages_status ON messages(status);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_messages_to_agent ON messages(to_agent, status);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_messages_correlation ON messages(correlation_id);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_messages_trace ON messages(trace_id);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_messages_expires ON messages(expires_at_ms);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_messages_priority ON messages(priority DESC, created_at_ms ASC);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_inbox_agent ON message_inbox(agent_id, delivered_at_ms);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_inbox_message ON message_inbox(message_id);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_human_requests_state ON human_requests(state);
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_agents_lease ON agents(lease_expires_ms);
|
|
81
|
+
|
|
82
|
+
-- Assign Job 테이블
|
|
83
|
+
CREATE TABLE IF NOT EXISTS assign_jobs (
|
|
84
|
+
job_id TEXT PRIMARY KEY,
|
|
85
|
+
supervisor_agent TEXT NOT NULL,
|
|
86
|
+
worker_agent TEXT NOT NULL,
|
|
87
|
+
topic TEXT NOT NULL DEFAULT 'assign.job',
|
|
88
|
+
task TEXT NOT NULL DEFAULT '',
|
|
89
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
90
|
+
status TEXT NOT NULL CHECK (status IN ('queued','running','succeeded','failed','timed_out')),
|
|
91
|
+
attempt INTEGER NOT NULL DEFAULT 1,
|
|
92
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
93
|
+
max_retries INTEGER NOT NULL DEFAULT 0,
|
|
94
|
+
priority INTEGER NOT NULL DEFAULT 5 CHECK (priority BETWEEN 1 AND 9),
|
|
95
|
+
ttl_ms INTEGER NOT NULL DEFAULT 600000,
|
|
96
|
+
timeout_ms INTEGER NOT NULL DEFAULT 600000,
|
|
97
|
+
deadline_ms INTEGER,
|
|
98
|
+
trace_id TEXT NOT NULL,
|
|
99
|
+
correlation_id TEXT NOT NULL,
|
|
100
|
+
last_message_id TEXT,
|
|
101
|
+
result_json TEXT,
|
|
102
|
+
error_json TEXT,
|
|
103
|
+
created_at_ms INTEGER NOT NULL,
|
|
104
|
+
updated_at_ms INTEGER NOT NULL,
|
|
105
|
+
started_at_ms INTEGER,
|
|
106
|
+
completed_at_ms INTEGER,
|
|
107
|
+
last_retry_at_ms INTEGER
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_assign_jobs_status ON assign_jobs(status, updated_at_ms DESC);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_assign_jobs_supervisor ON assign_jobs(supervisor_agent, updated_at_ms DESC);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_assign_jobs_worker ON assign_jobs(worker_agent, updated_at_ms DESC);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_assign_jobs_deadline ON assign_jobs(deadline_ms, status);
|
|
114
|
+
|
|
115
|
+
-- 파이프라인 상태 테이블 (Phase 2)
|
|
116
|
+
CREATE TABLE IF NOT EXISTS pipeline_state (
|
|
117
|
+
team_name TEXT PRIMARY KEY,
|
|
118
|
+
phase TEXT NOT NULL DEFAULT 'plan',
|
|
119
|
+
fix_attempt INTEGER DEFAULT 0,
|
|
120
|
+
fix_max INTEGER DEFAULT 3,
|
|
121
|
+
ralph_iteration INTEGER DEFAULT 0,
|
|
122
|
+
ralph_max INTEGER DEFAULT 10,
|
|
123
|
+
artifacts TEXT DEFAULT '{}',
|
|
124
|
+
phase_history TEXT DEFAULT '[]',
|
|
125
|
+
created_at INTEGER,
|
|
126
|
+
updated_at INTEGER
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
-- Reflexion 에러 학습 테이블
|
|
130
|
+
CREATE TABLE IF NOT EXISTS reflexion_entries (
|
|
131
|
+
id TEXT PRIMARY KEY,
|
|
132
|
+
type TEXT NOT NULL DEFAULT 'reflexion', -- reflexion | adaptive
|
|
133
|
+
error_pattern TEXT NOT NULL, -- 에러 시그니처 (정규화)
|
|
134
|
+
error_message TEXT NOT NULL, -- 원본 에러 메시지
|
|
135
|
+
context_json TEXT NOT NULL DEFAULT '{}', -- { file, function, cli, agent }
|
|
136
|
+
solution TEXT NOT NULL, -- 해결책 설명
|
|
137
|
+
solution_code TEXT, -- 해결 코드 스니펫 (있으면)
|
|
138
|
+
adaptive_state_json TEXT NOT NULL DEFAULT '{}', -- adaptive rule metadata
|
|
139
|
+
confidence REAL NOT NULL DEFAULT 0.5, -- 솔루션 신뢰도 (0-1)
|
|
140
|
+
hit_count INTEGER NOT NULL DEFAULT 1, -- 매칭 횟수
|
|
141
|
+
success_count INTEGER NOT NULL DEFAULT 0, -- 성공 횟수
|
|
142
|
+
last_hit_ms INTEGER NOT NULL,
|
|
143
|
+
created_at_ms INTEGER NOT NULL,
|
|
144
|
+
updated_at_ms INTEGER NOT NULL
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_reflexion_pattern ON reflexion_entries(error_pattern);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS idx_reflexion_confidence ON reflexion_entries(confidence DESC);
|
package/hub/store.mjs
CHANGED
|
@@ -381,7 +381,8 @@ export function createStore(dbPath, options = {}) {
|
|
|
381
381
|
}) {
|
|
382
382
|
const now = Date.now();
|
|
383
383
|
const leaseExpires = now + heartbeat_ttl_ms;
|
|
384
|
-
|
|
384
|
+
const leaseId = uuidv7();
|
|
385
|
+
const write = S.upsertAgent.run({
|
|
385
386
|
agent_id,
|
|
386
387
|
cli,
|
|
387
388
|
pid: pid ?? null,
|
|
@@ -392,11 +393,23 @@ export function createStore(dbPath, options = {}) {
|
|
|
392
393
|
status: "online",
|
|
393
394
|
metadata_json: JSON.stringify(metadata),
|
|
394
395
|
});
|
|
396
|
+
const persisted = parseAgentRow(S.getAgent.get(agent_id));
|
|
395
397
|
return {
|
|
396
398
|
agent_id,
|
|
397
|
-
lease_id:
|
|
399
|
+
lease_id: leaseId,
|
|
398
400
|
lease_expires_ms: leaseExpires,
|
|
399
401
|
server_time_ms: now,
|
|
402
|
+
effective: {
|
|
403
|
+
updated: write.changes > 0,
|
|
404
|
+
online:
|
|
405
|
+
persisted?.status === "online" &&
|
|
406
|
+
Number(persisted.lease_expires_ms) === leaseExpires,
|
|
407
|
+
status: persisted?.status ?? null,
|
|
408
|
+
last_seen_ms: persisted?.last_seen_ms ?? null,
|
|
409
|
+
lease_expires_ms: persisted?.lease_expires_ms ?? null,
|
|
410
|
+
store_type: "sqlite",
|
|
411
|
+
db_path: dbPath ?? null,
|
|
412
|
+
},
|
|
400
413
|
};
|
|
401
414
|
},
|
|
402
415
|
|
package/hub/tools.mjs
CHANGED
|
@@ -155,10 +155,7 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
155
155
|
},
|
|
156
156
|
},
|
|
157
157
|
},
|
|
158
|
-
handler: wrap("REGISTER_FAILED", (args) =>
|
|
159
|
-
const data = router.registerAgent(args);
|
|
160
|
-
return { ok: true, data };
|
|
161
|
-
}),
|
|
158
|
+
handler: wrap("REGISTER_FAILED", (args) => router.registerAgent(args)),
|
|
162
159
|
},
|
|
163
160
|
|
|
164
161
|
// ── 2. status ──
|