triflux 10.9.17 → 10.9.18
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/hub/team/handoff.mjs +17 -3
- package/hub/team/headless.mjs +76 -29
- package/hub/team/psmux.mjs +1 -0
- package/hub/team/swarm-hypervisor.mjs +238 -97
- package/hub/team/swarm-planner.mjs +2 -1
- package/package.json +8 -2
- package/scripts/__tests__/release-governance.test.mjs +148 -0
- package/scripts/headless-guard.mjs +1 -1
- package/scripts/release/bump-version.mjs +77 -0
- package/scripts/release/check-sync.mjs +51 -0
- package/scripts/release/lib.mjs +303 -0
- package/scripts/release/prepare.mjs +85 -0
- package/scripts/release/publish.mjs +87 -0
- package/scripts/release/verify.mjs +81 -0
- package/scripts/release/version-manifest.json +26 -0
- package/scripts/tfx-route.sh +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "triflux",
|
|
11
11
|
"description": "Tri-CLI orchestrator for Claude Code. Routes tasks across Claude + Codex + Gemini with consensus intelligence, natural language routing, 42 skills, and cross-model review.",
|
|
12
|
-
"version": "10.9.
|
|
12
|
+
"version": "10.9.18",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "tellang"
|
|
15
15
|
},
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
]
|
|
31
31
|
}
|
|
32
32
|
],
|
|
33
|
-
"version": "10.9.
|
|
33
|
+
"version": "10.9.18"
|
|
34
34
|
}
|
package/hub/team/handoff.mjs
CHANGED
|
@@ -219,6 +219,14 @@ export function validateHandoff(parsed, context = {}) {
|
|
|
219
219
|
);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
const filesChangedCount = Array.isArray(h.files_changed)
|
|
223
|
+
? h.files_changed.filter(Boolean).length
|
|
224
|
+
: 0;
|
|
225
|
+
if (filesChangedCount === 0 && h.lead_action === "accept") {
|
|
226
|
+
h.lead_action = "needs_read";
|
|
227
|
+
warnings.push("files_changed empty: accept → needs_read");
|
|
228
|
+
}
|
|
229
|
+
|
|
222
230
|
const missingCore = coreRequired.filter((f) => !h[f]);
|
|
223
231
|
const missingRouting = routingRequired.filter((f) => !h[f]);
|
|
224
232
|
const valid = missingCore.length === 0 && missingRouting.length === 0;
|
|
@@ -231,15 +239,20 @@ export function validateHandoff(parsed, context = {}) {
|
|
|
231
239
|
* @param {number} exitCode
|
|
232
240
|
* @param {string} resultFile
|
|
233
241
|
* @param {string} [cli]
|
|
242
|
+
* @param {object} [context]
|
|
243
|
+
* @param {string[]} [context.filesChanged]
|
|
234
244
|
* @returns {object}
|
|
235
245
|
*/
|
|
236
|
-
export function buildFallbackHandoff(exitCode, resultFile, cli) {
|
|
246
|
+
export function buildFallbackHandoff(exitCode, resultFile, cli, context = {}) {
|
|
237
247
|
const ok = exitCode === 0;
|
|
248
|
+
const filesChanged = Array.isArray(context.filesChanged)
|
|
249
|
+
? context.filesChanged.filter(Boolean)
|
|
250
|
+
: [];
|
|
238
251
|
return {
|
|
239
252
|
status: ok ? "ok" : "failed",
|
|
240
|
-
lead_action: ok ? "accept" : "retry",
|
|
253
|
+
lead_action: ok && filesChanged.length > 0 ? "accept" : ok ? "needs_read" : "retry",
|
|
241
254
|
task: "unknown",
|
|
242
|
-
files_changed:
|
|
255
|
+
files_changed: filesChanged,
|
|
243
256
|
verdict: `${cli || "worker"} completed (exit ${exitCode})`,
|
|
244
257
|
confidence: "low",
|
|
245
258
|
risk: "low",
|
|
@@ -298,6 +311,7 @@ export function processHandoff(rawText, context = {}) {
|
|
|
298
311
|
context.exitCode ?? 1,
|
|
299
312
|
context.resultFile || "none",
|
|
300
313
|
context.cli,
|
|
314
|
+
{ filesChanged: context.gitDiffFiles },
|
|
301
315
|
);
|
|
302
316
|
return {
|
|
303
317
|
handoff: fb,
|
package/hub/team/headless.mjs
CHANGED
|
@@ -22,7 +22,11 @@ import { getMaxSpawnPerSec } from "../lib/spawn-trace.mjs";
|
|
|
22
22
|
import { escapePwshSingleQuoted } from "../cli-adapter-base.mjs";
|
|
23
23
|
import { getBackend } from "./backend.mjs";
|
|
24
24
|
import { resolveDashboardLayout } from "./dashboard-layout.mjs";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
formatHandoffForLead,
|
|
27
|
+
HANDOFF_INSTRUCTION_SHORT,
|
|
28
|
+
processHandoff,
|
|
29
|
+
} from "./handoff.mjs";
|
|
26
30
|
import {
|
|
27
31
|
capturePsmuxPane,
|
|
28
32
|
createPsmuxSession,
|
|
@@ -317,7 +321,7 @@ export function createStallMonitor(paneId, resultFile, config, deps = {}) {
|
|
|
317
321
|
* @param {string} [opts.command] — re-dispatch용 원본 명령
|
|
318
322
|
* @param {string} [opts.token] — completion token
|
|
319
323
|
* @param {(snapshot: string) => void} [opts.onPoll] — 폴링 콜백
|
|
320
|
-
* @returns {Promise<{ matched: boolean, exitCode: number|null, restarts: number, stallDetected: boolean }>}
|
|
324
|
+
* @returns {Promise<{ matched: boolean, exitCode: number|null, restarts: number, stallDetected: boolean, paneId: string, token?: string, logPath?: string|null }>}
|
|
321
325
|
*/
|
|
322
326
|
export async function waitForCompletionWithStallDetect(
|
|
323
327
|
sessionName,
|
|
@@ -347,19 +351,23 @@ export async function waitForCompletionWithStallDetect(
|
|
|
347
351
|
const _startCapture = deps.startCapture || startCapture;
|
|
348
352
|
|
|
349
353
|
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
354
|
+
const buildCompletionRegex = (activeToken) => {
|
|
355
|
+
const completionPatterns = [
|
|
356
|
+
activeToken
|
|
357
|
+
? `${esc("__TRIFLUX_DONE__:")}${esc(activeToken)}:(\\d+)`
|
|
358
|
+
: `${esc("__TRIFLUX_DONE__:")}\\S+:(\\d+)`,
|
|
359
|
+
activeToken
|
|
360
|
+
? `${esc("TFX_DONE_")}${esc(activeToken)}:(\\d+)`
|
|
361
|
+
: `${esc("TFX_DONE_")}\\S+:(\\d+)`,
|
|
362
|
+
];
|
|
363
|
+
return new RegExp(completionPatterns.join("|"), "m");
|
|
364
|
+
};
|
|
359
365
|
|
|
360
366
|
let restarts = 0;
|
|
361
367
|
let currentPaneId = paneId;
|
|
362
368
|
let stallDetected = false;
|
|
369
|
+
let currentToken = token;
|
|
370
|
+
let currentLogPath = opts.logPath || null;
|
|
363
371
|
|
|
364
372
|
while (true) {
|
|
365
373
|
let lastOutput = "";
|
|
@@ -386,6 +394,9 @@ export async function waitForCompletionWithStallDetect(
|
|
|
386
394
|
restarts,
|
|
387
395
|
stallDetected,
|
|
388
396
|
timedOut: true,
|
|
397
|
+
paneId: currentPaneId,
|
|
398
|
+
token: currentToken,
|
|
399
|
+
logPath: currentLogPath,
|
|
389
400
|
};
|
|
390
401
|
}
|
|
391
402
|
|
|
@@ -400,6 +411,7 @@ export async function waitForCompletionWithStallDetect(
|
|
|
400
411
|
}
|
|
401
412
|
|
|
402
413
|
// 2) completion 토큰 감지
|
|
414
|
+
const completionRe = buildCompletionRegex(currentToken);
|
|
403
415
|
const completionMatch = completionRe.exec(currentOutput);
|
|
404
416
|
if (completionMatch) {
|
|
405
417
|
return {
|
|
@@ -411,6 +423,9 @@ export async function waitForCompletionWithStallDetect(
|
|
|
411
423
|
restarts,
|
|
412
424
|
stallDetected,
|
|
413
425
|
timedOut: false,
|
|
426
|
+
paneId: currentPaneId,
|
|
427
|
+
token: currentToken,
|
|
428
|
+
logPath: currentLogPath,
|
|
414
429
|
};
|
|
415
430
|
}
|
|
416
431
|
|
|
@@ -443,6 +458,9 @@ export async function waitForCompletionWithStallDetect(
|
|
|
443
458
|
restarts,
|
|
444
459
|
stallDetected,
|
|
445
460
|
timedOut: false,
|
|
461
|
+
paneId: currentPaneId,
|
|
462
|
+
token: currentToken,
|
|
463
|
+
logPath: currentLogPath,
|
|
446
464
|
};
|
|
447
465
|
}
|
|
448
466
|
} catch {
|
|
@@ -481,8 +499,10 @@ export async function waitForCompletionWithStallDetect(
|
|
|
481
499
|
"#{session_name}:#{window_index}.#{pane_index}",
|
|
482
500
|
]);
|
|
483
501
|
_startCapture(sessionName, newPaneId);
|
|
484
|
-
_dispatch(sessionName, newPaneId, command);
|
|
485
|
-
currentPaneId = newPaneId;
|
|
502
|
+
const redispatch = _dispatch(sessionName, newPaneId, command);
|
|
503
|
+
currentPaneId = redispatch?.paneId || newPaneId;
|
|
504
|
+
if (redispatch?.token) currentToken = redispatch.token;
|
|
505
|
+
if (redispatch?.logPath) currentLogPath = redispatch.logPath;
|
|
486
506
|
}
|
|
487
507
|
|
|
488
508
|
restarts++;
|
|
@@ -565,7 +585,11 @@ async function dispatchProgressive(sessionName, assignments, opts = {}) {
|
|
|
565
585
|
assignment.cli,
|
|
566
586
|
assignment.prompt,
|
|
567
587
|
resultFile,
|
|
568
|
-
{
|
|
588
|
+
{
|
|
589
|
+
mcp: assignment.mcp,
|
|
590
|
+
model: assignment.model,
|
|
591
|
+
cwd: assignment.cwd || assignment.workdir,
|
|
592
|
+
},
|
|
569
593
|
);
|
|
570
594
|
startCapture(sessionName, newPaneId);
|
|
571
595
|
// pane 간 pipe-pane EBUSY 방지 — 이벤트 루프 해방하며 순차 대기
|
|
@@ -585,6 +609,7 @@ async function dispatchProgressive(sessionName, assignments, opts = {}) {
|
|
|
585
609
|
role: assignment.role,
|
|
586
610
|
command: cmd,
|
|
587
611
|
workerId,
|
|
612
|
+
cwd: assignment.cwd || assignment.workdir,
|
|
588
613
|
});
|
|
589
614
|
}
|
|
590
615
|
|
|
@@ -636,7 +661,11 @@ async function dispatchBatch(sessionName, assignments, opts = {}) {
|
|
|
636
661
|
assignment.cli,
|
|
637
662
|
assignment.prompt,
|
|
638
663
|
resultFile,
|
|
639
|
-
{
|
|
664
|
+
{
|
|
665
|
+
mcp: assignment.mcp,
|
|
666
|
+
model: assignment.model,
|
|
667
|
+
cwd: assignment.cwd || assignment.workdir,
|
|
668
|
+
},
|
|
640
669
|
);
|
|
641
670
|
const scriptDir = join(RESULT_DIR, sessionName);
|
|
642
671
|
await registerHeadlessWorker(sessionName, i, assignment.cli);
|
|
@@ -661,6 +690,7 @@ async function dispatchBatch(sessionName, assignments, opts = {}) {
|
|
|
661
690
|
role: assignment.role,
|
|
662
691
|
command: cmd,
|
|
663
692
|
workerId,
|
|
693
|
+
cwd: assignment.cwd || assignment.workdir,
|
|
664
694
|
};
|
|
665
695
|
}),
|
|
666
696
|
);
|
|
@@ -740,6 +770,9 @@ async function awaitAll(
|
|
|
740
770
|
onPoll: stallPollCb,
|
|
741
771
|
},
|
|
742
772
|
);
|
|
773
|
+
if (stallResult.paneId) d.paneId = stallResult.paneId;
|
|
774
|
+
if (stallResult.token) d.token = stallResult.token;
|
|
775
|
+
if (stallResult.logPath) d.logPath = stallResult.logPath;
|
|
743
776
|
completion = {
|
|
744
777
|
matched: stallResult.matched,
|
|
745
778
|
exitCode: stallResult.exitCode,
|
|
@@ -799,28 +832,27 @@ async function awaitAll(
|
|
|
799
832
|
* @returns {Array}
|
|
800
833
|
*/
|
|
801
834
|
async function collectResults(sessionName, results) {
|
|
802
|
-
// B3 fix: git diff를 루프 밖에서 1회만 실행 (워커 수만큼 중복 방지)
|
|
803
|
-
let gitDiffFiles;
|
|
804
|
-
try {
|
|
805
|
-
const diffOut = execSync("git diff --name-only HEAD", {
|
|
806
|
-
encoding: "utf8",
|
|
807
|
-
timeout: 5000,
|
|
808
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
809
|
-
});
|
|
810
|
-
gitDiffFiles = diffOut.trim().split("\n").filter(Boolean);
|
|
811
|
-
} catch {
|
|
812
|
-
/* git 미설치 또는 non-repo — 무시 */
|
|
813
|
-
}
|
|
814
|
-
|
|
815
835
|
// handoff 파이프라인: parse → validate → format (각 워커 결과에 적용)
|
|
816
836
|
return await Promise.all(
|
|
817
837
|
results.map(async ({ d, completion, output }) => {
|
|
838
|
+
const workerGitDiffFiles = collectGitDiffFiles(d.cwd);
|
|
818
839
|
const handoffResult = processHandoff(output, {
|
|
819
840
|
exitCode: completion.exitCode,
|
|
820
841
|
resultFile: d.resultFile,
|
|
821
842
|
cli: d.cli,
|
|
822
|
-
gitDiffFiles,
|
|
843
|
+
gitDiffFiles: workerGitDiffFiles,
|
|
823
844
|
});
|
|
845
|
+
if (
|
|
846
|
+
completion.exitCode === 0 &&
|
|
847
|
+
workerGitDiffFiles.length === 0 &&
|
|
848
|
+
handoffResult.handoff?.lead_action === "accept"
|
|
849
|
+
) {
|
|
850
|
+
handoffResult.handoff = {
|
|
851
|
+
...handoffResult.handoff,
|
|
852
|
+
lead_action: "needs_read",
|
|
853
|
+
};
|
|
854
|
+
handoffResult.formatted = formatHandoffForLead(handoffResult.handoff);
|
|
855
|
+
}
|
|
824
856
|
const status =
|
|
825
857
|
handoffResult.handoff?.status ||
|
|
826
858
|
(completion.matched && completion.exitCode === 0
|
|
@@ -843,6 +875,7 @@ async function collectResults(sessionName, results) {
|
|
|
843
875
|
exitCode: completion.exitCode,
|
|
844
876
|
output,
|
|
845
877
|
resultFile: d.resultFile,
|
|
878
|
+
workerGitDiffFiles,
|
|
846
879
|
sessionDead: completion.sessionDead || false,
|
|
847
880
|
handoff: handoffResult.handoff,
|
|
848
881
|
handoffFormatted: handoffResult.formatted,
|
|
@@ -853,6 +886,20 @@ async function collectResults(sessionName, results) {
|
|
|
853
886
|
);
|
|
854
887
|
}
|
|
855
888
|
|
|
889
|
+
function collectGitDiffFiles(cwd) {
|
|
890
|
+
try {
|
|
891
|
+
const diffOut = execSync("git diff --name-only HEAD", {
|
|
892
|
+
cwd,
|
|
893
|
+
encoding: "utf8",
|
|
894
|
+
timeout: 5000,
|
|
895
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
896
|
+
});
|
|
897
|
+
return diffOut.trim().split("\n").filter(Boolean);
|
|
898
|
+
} catch {
|
|
899
|
+
return [];
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
856
903
|
/**
|
|
857
904
|
* 헤드리스 CLI 오케스트레이션 실행
|
|
858
905
|
*
|
package/hub/team/psmux.mjs
CHANGED
|
@@ -1273,6 +1273,7 @@ export async function waitForPattern(
|
|
|
1273
1273
|
|
|
1274
1274
|
/**
|
|
1275
1275
|
* 완료 토큰이 찍힐 때까지 대기하고 exit code를 파싱한다.
|
|
1276
|
+
* NOTE: 주 채널은 headless.waitForCompletionWithStallDetect이며, 본 함수는 fallback 채널이다.
|
|
1276
1277
|
* @param {string} sessionName
|
|
1277
1278
|
* @param {string} paneNameOrTarget
|
|
1278
1279
|
* @param {string} token
|