triflux 3.3.0-dev.3 → 3.3.0-dev.6
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/hub/assign-callbacks.mjs +136 -0
- package/hub/bridge.mjs +289 -98
- package/hub/pipe.mjs +81 -0
- package/hub/server.mjs +159 -133
- package/hub/store.mjs +36 -2
- package/hub/team/cli-team-status.mjs +17 -3
- package/hub/team/native-supervisor.mjs +62 -22
- package/hub/team/native.mjs +266 -200
- package/hub/team/nativeProxy.mjs +173 -72
- package/hub/tools.mjs +6 -6
- package/hub/workers/delegator-mcp.mjs +285 -140
- package/package.json +60 -60
- package/scripts/completions/tfx.bash +47 -0
- package/scripts/completions/tfx.fish +44 -0
- package/scripts/completions/tfx.zsh +83 -0
- package/scripts/lib/mcp-filter.mjs +642 -0
- package/scripts/lib/mcp-server-catalog.mjs +118 -0
- package/scripts/mcp-check.mjs +126 -88
- package/scripts/test-tfx-route-no-claude-native.mjs +10 -2
- package/scripts/tfx-route.sh +504 -180
- package/skills/tfx-auto/SKILL.md +6 -1
package/hub/store.mjs
CHANGED
|
@@ -223,6 +223,24 @@ export function createStore(dbPath) {
|
|
|
223
223
|
activeAssignCount: db.prepare("SELECT COUNT(*) as cnt FROM assign_jobs WHERE status IN ('queued','running')"),
|
|
224
224
|
};
|
|
225
225
|
|
|
226
|
+
const assignStatusListeners = new Set();
|
|
227
|
+
|
|
228
|
+
function buildAssignCallbackEvent(row) {
|
|
229
|
+
return {
|
|
230
|
+
job_id: row.job_id,
|
|
231
|
+
status: row.status,
|
|
232
|
+
result: row.result ?? row.error ?? null,
|
|
233
|
+
timestamp: new Date(row.updated_at_ms || Date.now()).toISOString(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function notifyAssignStatusListeners(row) {
|
|
238
|
+
const event = buildAssignCallbackEvent(row);
|
|
239
|
+
for (const listener of Array.from(assignStatusListeners)) {
|
|
240
|
+
try { listener(event, row); } catch {}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
226
244
|
function clampMaxMessages(value, fallback = 20) {
|
|
227
245
|
const num = Number(value);
|
|
228
246
|
if (!Number.isFinite(num)) return fallback;
|
|
@@ -481,7 +499,9 @@ export function createStore(dbPath) {
|
|
|
481
499
|
last_retry_at_ms: retry_count > 0 ? now : null,
|
|
482
500
|
};
|
|
483
501
|
S.insertAssign.run(row);
|
|
484
|
-
|
|
502
|
+
const inserted = store.getAssign(row.job_id);
|
|
503
|
+
notifyAssignStatusListeners(inserted);
|
|
504
|
+
return inserted;
|
|
485
505
|
},
|
|
486
506
|
|
|
487
507
|
getAssign(jobId) {
|
|
@@ -541,7 +561,11 @@ export function createStore(dbPath) {
|
|
|
541
561
|
: current.last_retry_at_ms,
|
|
542
562
|
};
|
|
543
563
|
S.updateAssign.run(nextRow);
|
|
544
|
-
|
|
564
|
+
const updated = store.getAssign(jobId);
|
|
565
|
+
if (updated && current.status !== updated.status) {
|
|
566
|
+
notifyAssignStatusListeners(updated);
|
|
567
|
+
}
|
|
568
|
+
return updated;
|
|
545
569
|
},
|
|
546
570
|
|
|
547
571
|
listAssigns({
|
|
@@ -639,6 +663,16 @@ export function createStore(dbPath) {
|
|
|
639
663
|
};
|
|
640
664
|
},
|
|
641
665
|
|
|
666
|
+
onAssignStatusChange(listener) {
|
|
667
|
+
if (typeof listener !== 'function') {
|
|
668
|
+
return () => {};
|
|
669
|
+
}
|
|
670
|
+
assignStatusListeners.add(listener);
|
|
671
|
+
return () => {
|
|
672
|
+
assignStatusListeners.delete(listener);
|
|
673
|
+
};
|
|
674
|
+
},
|
|
675
|
+
|
|
642
676
|
getDeliveryStats() {
|
|
643
677
|
return {
|
|
644
678
|
total_deliveries: S.ackedRecent.get(Date.now()).cnt,
|
|
@@ -54,6 +54,21 @@ function renderTasks(tasks = []) {
|
|
|
54
54
|
console.log("");
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function formatCompletionSuffix(member) {
|
|
58
|
+
if (!member?.completionStatus) return "";
|
|
59
|
+
if (member.completionStatus === "abnormal") {
|
|
60
|
+
const reason = member.completionReason || "unknown";
|
|
61
|
+
return ` ${RED}[abnormal:${reason}]${RESET}`;
|
|
62
|
+
}
|
|
63
|
+
if (member.completionStatus === "normal") {
|
|
64
|
+
return ` ${GREEN}[route-ok]${RESET}`;
|
|
65
|
+
}
|
|
66
|
+
if (member.completionStatus === "unchecked") {
|
|
67
|
+
return ` ${GRAY}[route-unchecked]${RESET}`;
|
|
68
|
+
}
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
export async function teamStatus() {
|
|
58
73
|
const state = loadTeamState();
|
|
59
74
|
if (!state) {
|
|
@@ -91,7 +106,7 @@ export async function teamStatus() {
|
|
|
91
106
|
if (nativeMembers.length) {
|
|
92
107
|
console.log("");
|
|
93
108
|
for (const m of nativeMembers) {
|
|
94
|
-
console.log(` • ${m.name}: ${m.status}${m.lastPreview ? ` ${DIM}${m.lastPreview}${RESET}` : ""}`);
|
|
109
|
+
console.log(` • ${m.name}: ${m.status}${formatCompletionSuffix(m)}${m.lastPreview ? ` ${DIM}${m.lastPreview}${RESET}` : ""}`);
|
|
95
110
|
}
|
|
96
111
|
}
|
|
97
112
|
}
|
|
@@ -216,7 +231,7 @@ export async function teamDebug() {
|
|
|
216
231
|
console.log(` ${DIM}(no data)${RESET}`);
|
|
217
232
|
} else {
|
|
218
233
|
for (const m of members) {
|
|
219
|
-
console.log(` - ${m.name}: ${m.status}${m.lastPreview ? ` ${DIM}${m.lastPreview}${RESET}` : ""}`);
|
|
234
|
+
console.log(` - ${m.name}: ${m.status}${formatCompletionSuffix(m)}${m.lastPreview ? ` ${DIM}${m.lastPreview}${RESET}` : ""}`);
|
|
220
235
|
}
|
|
221
236
|
}
|
|
222
237
|
console.log("");
|
|
@@ -266,4 +281,3 @@ export function teamList() {
|
|
|
266
281
|
}
|
|
267
282
|
console.log("");
|
|
268
283
|
}
|
|
269
|
-
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// hub/team/native-supervisor.mjs — tmux 없이 멀티 CLI를 직접 띄우는 네이티브 팀 런타임
|
|
2
|
-
import { createServer } from "node:http";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { mkdirSync, readFileSync, writeFileSync, createWriteStream } from "node:fs";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync, createWriteStream } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { verifySlimWrapperRouteExecution } from "./native.mjs";
|
|
7
|
+
|
|
8
|
+
const ROUTE_LOG_TAIL_BYTES = 65536;
|
|
6
9
|
|
|
7
10
|
function parseArgs(argv) {
|
|
8
11
|
const out = {};
|
|
@@ -19,10 +22,43 @@ async function readJson(path) {
|
|
|
19
22
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
function safeText(v, fallback = "") {
|
|
23
|
-
if (v == null) return fallback;
|
|
24
|
-
return String(v);
|
|
25
|
-
}
|
|
25
|
+
function safeText(v, fallback = "") {
|
|
26
|
+
if (v == null) return fallback;
|
|
27
|
+
return String(v);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readTailText(path, maxBytes = ROUTE_LOG_TAIL_BYTES) {
|
|
31
|
+
try {
|
|
32
|
+
const raw = readFileSync(path, "utf8");
|
|
33
|
+
if (raw.length <= maxBytes) return raw;
|
|
34
|
+
return raw.slice(-maxBytes);
|
|
35
|
+
} catch {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function finalizeRouteVerification(state) {
|
|
41
|
+
if (state?.member?.role !== "worker") return;
|
|
42
|
+
|
|
43
|
+
const verification = verifySlimWrapperRouteExecution({
|
|
44
|
+
promptText: safeText(state.member?.prompt),
|
|
45
|
+
stdoutText: readTailText(state.logFile),
|
|
46
|
+
stderrText: readTailText(state.errFile),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
state.routeVerification = verification;
|
|
50
|
+
if (!verification.expectedRouteInvocation) {
|
|
51
|
+
state.completionStatus = "unchecked";
|
|
52
|
+
state.completionReason = null;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
state.completionStatus = verification.abnormal ? "abnormal" : "normal";
|
|
57
|
+
state.completionReason = verification.reason;
|
|
58
|
+
if (verification.abnormal) {
|
|
59
|
+
state.lastPreview = "[abnormal] tfx-route.sh evidence missing";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
26
62
|
|
|
27
63
|
function nowMs() {
|
|
28
64
|
return Date.now();
|
|
@@ -60,13 +96,16 @@ function memberStateSnapshot() {
|
|
|
60
96
|
agentId: m.agentId,
|
|
61
97
|
command: m.command,
|
|
62
98
|
pid: state?.child?.pid || null,
|
|
63
|
-
status: state?.status || "unknown",
|
|
64
|
-
exitCode: state?.exitCode ?? null,
|
|
65
|
-
lastPreview: state?.lastPreview || "",
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
99
|
+
status: state?.status || "unknown",
|
|
100
|
+
exitCode: state?.exitCode ?? null,
|
|
101
|
+
lastPreview: state?.lastPreview || "",
|
|
102
|
+
completionStatus: state?.completionStatus || null,
|
|
103
|
+
completionReason: state?.completionReason || null,
|
|
104
|
+
routeVerification: state?.routeVerification || null,
|
|
105
|
+
logFile: state?.logFile || null,
|
|
106
|
+
errFile: state?.errFile || null,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
70
109
|
return states;
|
|
71
110
|
}
|
|
72
111
|
|
|
@@ -128,13 +167,14 @@ function spawnMember(member) {
|
|
|
128
167
|
}
|
|
129
168
|
});
|
|
130
169
|
|
|
131
|
-
child.on("exit", (code) => {
|
|
132
|
-
state.status = "exited";
|
|
133
|
-
state.exitCode = code;
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
|
|
137
|
-
|
|
170
|
+
child.on("exit", (code) => {
|
|
171
|
+
state.status = "exited";
|
|
172
|
+
state.exitCode = code;
|
|
173
|
+
finalizeRouteVerification(state);
|
|
174
|
+
try { outWs.end(); } catch {}
|
|
175
|
+
try { errWs.end(); } catch {}
|
|
176
|
+
maybeAutoShutdown();
|
|
177
|
+
});
|
|
138
178
|
|
|
139
179
|
processMap.set(member.name, state);
|
|
140
180
|
}
|