triflux 3.3.0-dev.7 → 4.0.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.ko.md +108 -199
- package/README.md +108 -199
- package/bin/triflux.mjs +2415 -1762
- package/hooks/keyword-rules.json +361 -354
- package/hooks/pipeline-stop.mjs +5 -2
- package/hub/assign-callbacks.mjs +136 -136
- package/hub/bridge.mjs +734 -708
- package/hub/delegator/contracts.mjs +38 -0
- package/hub/delegator/index.mjs +14 -0
- package/hub/delegator/schema/delegator-tools.schema.json +250 -0
- package/hub/delegator/service.mjs +302 -0
- package/hub/delegator/tool-definitions.mjs +35 -0
- package/hub/hitl.mjs +67 -67
- package/hub/paths.mjs +28 -0
- package/hub/pipe.mjs +589 -561
- package/hub/pipeline/state.mjs +23 -0
- package/hub/public/dashboard.html +349 -0
- package/hub/public/tray-icon.ico +0 -0
- package/hub/public/tray-icon.png +0 -0
- package/hub/router.mjs +782 -782
- package/hub/schema.sql +40 -40
- package/hub/server.mjs +810 -637
- package/hub/store.mjs +706 -706
- package/hub/team/cli/commands/attach.mjs +37 -0
- package/hub/team/cli/commands/control.mjs +43 -0
- package/hub/team/cli/commands/debug.mjs +74 -0
- package/hub/team/cli/commands/focus.mjs +53 -0
- package/hub/team/cli/commands/interrupt.mjs +36 -0
- package/hub/team/cli/commands/kill.mjs +37 -0
- package/hub/team/cli/commands/list.mjs +24 -0
- package/hub/team/cli/commands/send.mjs +37 -0
- package/hub/team/cli/commands/start/index.mjs +87 -0
- package/hub/team/cli/commands/start/parse-args.mjs +32 -0
- package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
- package/hub/team/cli/commands/start/start-mux.mjs +73 -0
- package/hub/team/cli/commands/start/start-wt.mjs +69 -0
- package/hub/team/cli/commands/status.mjs +87 -0
- package/hub/team/cli/commands/stop.mjs +31 -0
- package/hub/team/cli/commands/task.mjs +30 -0
- package/hub/team/cli/commands/tasks.mjs +13 -0
- package/hub/team/{cli.mjs → cli/help.mjs} +38 -99
- package/hub/team/cli/index.mjs +39 -0
- package/hub/team/cli/manifest.mjs +28 -0
- package/hub/team/cli/render.mjs +30 -0
- package/hub/team/cli/services/attach-fallback.mjs +54 -0
- package/hub/team/cli/services/hub-client.mjs +171 -0
- package/hub/team/cli/services/member-selector.mjs +30 -0
- package/hub/team/cli/services/native-control.mjs +115 -0
- package/hub/team/cli/services/runtime-mode.mjs +60 -0
- package/hub/team/cli/services/state-store.mjs +34 -0
- package/hub/team/cli/services/task-model.mjs +30 -0
- package/hub/team/native-supervisor.mjs +69 -63
- package/hub/team/native.mjs +367 -266
- package/hub/team/nativeProxy.mjs +217 -173
- package/hub/team/pane.mjs +149 -149
- package/hub/team/psmux.mjs +946 -946
- package/hub/team/session.mjs +608 -608
- package/hub/team/staleState.mjs +369 -299
- package/hub/tools.mjs +107 -107
- package/hub/tray.mjs +332 -0
- package/hub/workers/claude-worker.mjs +446 -446
- package/hub/workers/codex-mcp.mjs +414 -414
- package/hub/workers/delegator-mcp.mjs +1045 -1045
- package/hub/workers/factory.mjs +21 -21
- package/hub/workers/gemini-worker.mjs +349 -349
- package/hub/workers/interface.mjs +41 -41
- package/package.json +61 -60
- package/scripts/__tests__/keyword-detector.test.mjs +234 -234
- package/scripts/hub-ensure.mjs +102 -101
- package/scripts/keyword-detector.mjs +272 -272
- package/scripts/keyword-rules-expander.mjs +521 -521
- package/scripts/lib/keyword-rules.mjs +168 -168
- package/scripts/lib/mcp-filter.mjs +642 -642
- package/scripts/lib/mcp-server-catalog.mjs +118 -118
- package/scripts/mcp-check.mjs +126 -126
- package/scripts/preflight-cache.mjs +19 -0
- package/scripts/run.cjs +62 -62
- package/scripts/setup.mjs +68 -31
- package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
- package/scripts/tfx-route-worker.mjs +161 -161
- package/scripts/tfx-route.sh +1360 -1326
- package/skills/tfx-auto/SKILL.md +196 -196
- package/skills/tfx-auto-codex/SKILL.md +77 -77
- package/skills/tfx-multi/SKILL.md +378 -378
- package/hub/team/cli-team-common.mjs +0 -348
- package/hub/team/cli-team-control.mjs +0 -393
- package/hub/team/cli-team-start.mjs +0 -516
- package/hub/team/cli-team-status.mjs +0 -283
- package/skills/auto-verify/SKILL.md +0 -145
- package/skills/manage-skills/SKILL.md +0 -192
- package/skills/verify-implementation/SKILL.md +0 -138
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
import { buildLeadPrompt, buildPrompt } from "../../orchestrator.mjs";
|
|
6
|
+
import { HUB_PID_DIR, PKG_ROOT } from "./state-store.mjs";
|
|
7
|
+
|
|
8
|
+
export function buildNativeCliCommand(cli) {
|
|
9
|
+
switch (cli) {
|
|
10
|
+
case "codex":
|
|
11
|
+
return "codex --dangerously-bypass-approvals-and-sandbox --no-alt-screen";
|
|
12
|
+
case "gemini":
|
|
13
|
+
return "gemini";
|
|
14
|
+
case "claude":
|
|
15
|
+
return "claude";
|
|
16
|
+
default:
|
|
17
|
+
return cli;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function startNativeSupervisor({ sessionId, task, lead, agents, subtasks, hubUrl }) {
|
|
22
|
+
const configPath = join(HUB_PID_DIR, `team-native-${sessionId}.config.json`);
|
|
23
|
+
const runtimePath = join(HUB_PID_DIR, `team-native-${sessionId}.runtime.json`);
|
|
24
|
+
const logsDir = join(HUB_PID_DIR, "team-logs", sessionId);
|
|
25
|
+
mkdirSync(logsDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
const leadMember = {
|
|
28
|
+
role: "lead",
|
|
29
|
+
name: "lead",
|
|
30
|
+
cli: lead,
|
|
31
|
+
agentId: `${lead}-lead`,
|
|
32
|
+
command: buildNativeCliCommand(lead),
|
|
33
|
+
};
|
|
34
|
+
const workers = agents.map((cli, index) => ({
|
|
35
|
+
role: "worker",
|
|
36
|
+
name: `${cli}-${index + 1}`,
|
|
37
|
+
cli,
|
|
38
|
+
agentId: `${cli}-w${index + 1}`,
|
|
39
|
+
command: buildNativeCliCommand(cli),
|
|
40
|
+
subtask: subtasks[index],
|
|
41
|
+
}));
|
|
42
|
+
const members = [
|
|
43
|
+
{
|
|
44
|
+
...leadMember,
|
|
45
|
+
prompt: buildLeadPrompt(task, {
|
|
46
|
+
agentId: leadMember.agentId,
|
|
47
|
+
hubUrl,
|
|
48
|
+
teammateMode: "in-process",
|
|
49
|
+
workers: workers.map((worker) => ({
|
|
50
|
+
agentId: worker.agentId,
|
|
51
|
+
cli: worker.cli,
|
|
52
|
+
subtask: worker.subtask,
|
|
53
|
+
})),
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
...workers.map((worker) => ({
|
|
57
|
+
...worker,
|
|
58
|
+
prompt: buildPrompt(worker.subtask, { cli: worker.cli, agentId: worker.agentId, hubUrl }),
|
|
59
|
+
})),
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
writeFileSync(configPath, JSON.stringify({
|
|
63
|
+
sessionName: sessionId,
|
|
64
|
+
hubUrl,
|
|
65
|
+
startupDelayMs: 3000,
|
|
66
|
+
logsDir,
|
|
67
|
+
runtimeFile: runtimePath,
|
|
68
|
+
members,
|
|
69
|
+
}, null, 2) + "\n");
|
|
70
|
+
|
|
71
|
+
const child = spawn(process.execPath, [join(PKG_ROOT, "hub", "team", "native-supervisor.mjs"), "--config", configPath], {
|
|
72
|
+
detached: true,
|
|
73
|
+
stdio: "ignore",
|
|
74
|
+
env: { ...process.env },
|
|
75
|
+
windowsHide: true,
|
|
76
|
+
});
|
|
77
|
+
child.unref();
|
|
78
|
+
|
|
79
|
+
const deadline = Date.now() + 5000;
|
|
80
|
+
while (Date.now() < deadline) {
|
|
81
|
+
if (existsSync(runtimePath)) {
|
|
82
|
+
try {
|
|
83
|
+
const runtime = JSON.parse(readFileSync(runtimePath, "utf8"));
|
|
84
|
+
return { runtime, members };
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { runtime: null, members };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function nativeRequest(state, path, body = {}) {
|
|
94
|
+
if (!state?.native?.controlUrl) return null;
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch(`${state.native.controlUrl}${path}`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify(body),
|
|
100
|
+
});
|
|
101
|
+
return await res.json();
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function nativeGetStatus(state) {
|
|
108
|
+
if (!state?.native?.controlUrl) return null;
|
|
109
|
+
try {
|
|
110
|
+
const res = await fetch(`${state.native.controlUrl}/status`);
|
|
111
|
+
return await res.json();
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectMultiplexer,
|
|
3
|
+
hasWindowsTerminal,
|
|
4
|
+
hasWindowsTerminalSession,
|
|
5
|
+
sessionExists,
|
|
6
|
+
} from "../../session.mjs";
|
|
7
|
+
|
|
8
|
+
export function normalizeTeammateMode(mode = "auto") {
|
|
9
|
+
const raw = String(mode).toLowerCase();
|
|
10
|
+
if (raw === "inline" || raw === "native") return "in-process";
|
|
11
|
+
if (raw === "in-process" || raw === "tmux" || raw === "wt" || raw === "psmux") return raw;
|
|
12
|
+
if (raw === "windows-terminal" || raw === "windows_terminal") return "wt";
|
|
13
|
+
if (raw === "auto") {
|
|
14
|
+
if (process.env.TMUX) return "tmux";
|
|
15
|
+
return detectMultiplexer() === "psmux" ? "psmux" : "in-process";
|
|
16
|
+
}
|
|
17
|
+
return "in-process";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function normalizeLayout(layout = "2x2") {
|
|
21
|
+
const raw = String(layout).toLowerCase();
|
|
22
|
+
if (raw === "2x2" || raw === "grid") return "2x2";
|
|
23
|
+
if (raw === "1xn" || raw === "1x3" || raw === "vertical" || raw === "columns") return "1xN";
|
|
24
|
+
if (raw === "nx1" || raw === "horizontal" || raw === "rows") return "Nx1";
|
|
25
|
+
return "2x2";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isNativeMode(state) {
|
|
29
|
+
return state?.teammateMode === "in-process" && !!state?.native?.controlUrl;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isWtMode(state) {
|
|
33
|
+
return state?.teammateMode === "wt";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isTeamAlive(state) {
|
|
37
|
+
if (!state) return false;
|
|
38
|
+
if (isNativeMode(state)) {
|
|
39
|
+
try {
|
|
40
|
+
process.kill(state.native.supervisorPid, 0);
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (isWtMode(state)) {
|
|
47
|
+
if (!hasWindowsTerminal()) return false;
|
|
48
|
+
if (hasWindowsTerminalSession()) return true;
|
|
49
|
+
return Array.isArray(state.members) && state.members.length > 0;
|
|
50
|
+
}
|
|
51
|
+
return sessionExists(state.sessionName);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function ensureTmuxOrExit() {
|
|
55
|
+
const mux = detectMultiplexer();
|
|
56
|
+
if (mux) return mux;
|
|
57
|
+
const error = new Error("tmux 미발견");
|
|
58
|
+
error.code = "TMUX_REQUIRED";
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
export const PKG_ROOT = fileURLToPath(new URL("../../../../", import.meta.url));
|
|
8
|
+
export const HUB_PID_DIR = join(homedir(), ".claude", "cache", "tfx-hub");
|
|
9
|
+
export const TEAM_PROFILE = (() => {
|
|
10
|
+
const raw = String(process.env.TFX_TEAM_PROFILE || "team").trim().toLowerCase();
|
|
11
|
+
return raw === "codex-team" ? "codex-team" : "team";
|
|
12
|
+
})();
|
|
13
|
+
|
|
14
|
+
const TEAM_STATE_FILE = join(
|
|
15
|
+
HUB_PID_DIR,
|
|
16
|
+
TEAM_PROFILE === "codex-team" ? "team-state-codex-team.json" : "team-state.json",
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export function loadTeamState() {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(TEAM_STATE_FILE, "utf8"));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function saveTeamState(state) {
|
|
28
|
+
mkdirSync(dirname(TEAM_STATE_FILE), { recursive: true });
|
|
29
|
+
writeFileSync(TEAM_STATE_FILE, JSON.stringify({ ...state, profile: TEAM_PROFILE }, null, 2) + "\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function clearTeamState() {
|
|
33
|
+
try { unlinkSync(TEAM_STATE_FILE); } catch {}
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function buildTasks(subtasks, workers) {
|
|
2
|
+
return subtasks.map((subtask, index) => ({
|
|
3
|
+
id: `T${index + 1}`,
|
|
4
|
+
title: subtask,
|
|
5
|
+
owner: workers[index]?.name || null,
|
|
6
|
+
status: "pending",
|
|
7
|
+
depends_on: index === 0 ? [] : [`T${index}`],
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function normalizeTaskStatus(action) {
|
|
12
|
+
const value = String(action || "").toLowerCase();
|
|
13
|
+
if (value === "done" || value === "complete" || value === "completed") return "completed";
|
|
14
|
+
if (value === "progress" || value === "in-progress" || value === "in_progress") return "in_progress";
|
|
15
|
+
if (value === "pending") return "pending";
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function updateTaskStatus(tasks = [], taskId, nextStatus) {
|
|
20
|
+
const normalizedId = String(taskId || "").toUpperCase();
|
|
21
|
+
const target = tasks.find((task) => String(task.id).toUpperCase() === normalizedId);
|
|
22
|
+
if (!target) return { tasks, target: null };
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
target: { ...target, status: nextStatus },
|
|
26
|
+
tasks: tasks.map((task) => (
|
|
27
|
+
String(task.id).toUpperCase() === normalizedId ? { ...task, status: nextStatus } : task
|
|
28
|
+
)),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
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";
|
|
6
|
-
import { verifySlimWrapperRouteExecution } from "./native.mjs";
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
import { forceCleanupTeam } from "./nativeProxy.mjs";
|
|
8
|
+
|
|
9
|
+
const ROUTE_LOG_TAIL_BYTES = 65536;
|
|
9
10
|
|
|
10
11
|
function parseArgs(argv) {
|
|
11
12
|
const out = {};
|
|
@@ -22,43 +23,43 @@ async function readJson(path) {
|
|
|
22
23
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
23
24
|
}
|
|
24
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
|
+
function safeText(v, fallback = "") {
|
|
27
|
+
if (v == null) return fallback;
|
|
28
|
+
return String(v);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readTailText(path, maxBytes = ROUTE_LOG_TAIL_BYTES) {
|
|
32
|
+
try {
|
|
33
|
+
const raw = readFileSync(path, "utf8");
|
|
34
|
+
if (raw.length <= maxBytes) return raw;
|
|
35
|
+
return raw.slice(-maxBytes);
|
|
36
|
+
} catch {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function finalizeRouteVerification(state) {
|
|
42
|
+
if (state?.member?.role !== "worker") return;
|
|
43
|
+
|
|
44
|
+
const verification = verifySlimWrapperRouteExecution({
|
|
45
|
+
promptText: safeText(state.member?.prompt),
|
|
46
|
+
stdoutText: readTailText(state.logFile),
|
|
47
|
+
stderrText: readTailText(state.errFile),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
state.routeVerification = verification;
|
|
51
|
+
if (!verification.expectedRouteInvocation) {
|
|
52
|
+
state.completionStatus = "unchecked";
|
|
53
|
+
state.completionReason = null;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
state.completionStatus = verification.abnormal ? "abnormal" : "normal";
|
|
58
|
+
state.completionReason = verification.reason;
|
|
59
|
+
if (verification.abnormal) {
|
|
60
|
+
state.lastPreview = "[abnormal] tfx-route.sh evidence missing";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
62
63
|
|
|
63
64
|
function nowMs() {
|
|
64
65
|
return Date.now();
|
|
@@ -73,6 +74,7 @@ if (!args.config) {
|
|
|
73
74
|
const config = await readJson(args.config);
|
|
74
75
|
const {
|
|
75
76
|
sessionName,
|
|
77
|
+
teamName = sessionName,
|
|
76
78
|
runtimeFile,
|
|
77
79
|
logsDir,
|
|
78
80
|
startupDelayMs = 3000,
|
|
@@ -96,16 +98,16 @@ function memberStateSnapshot() {
|
|
|
96
98
|
agentId: m.agentId,
|
|
97
99
|
command: m.command,
|
|
98
100
|
pid: state?.child?.pid || null,
|
|
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
|
-
}
|
|
101
|
+
status: state?.status || "unknown",
|
|
102
|
+
exitCode: state?.exitCode ?? null,
|
|
103
|
+
lastPreview: state?.lastPreview || "",
|
|
104
|
+
completionStatus: state?.completionStatus || null,
|
|
105
|
+
completionReason: state?.completionReason || null,
|
|
106
|
+
routeVerification: state?.routeVerification || null,
|
|
107
|
+
logFile: state?.logFile || null,
|
|
108
|
+
errFile: state?.errFile || null,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
109
111
|
return states;
|
|
110
112
|
}
|
|
111
113
|
|
|
@@ -167,14 +169,14 @@ function spawnMember(member) {
|
|
|
167
169
|
}
|
|
168
170
|
});
|
|
169
171
|
|
|
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
|
-
});
|
|
172
|
+
child.on("exit", (code) => {
|
|
173
|
+
state.status = "exited";
|
|
174
|
+
state.exitCode = code;
|
|
175
|
+
finalizeRouteVerification(state);
|
|
176
|
+
try { outWs.end(); } catch {}
|
|
177
|
+
try { errWs.end(); } catch {}
|
|
178
|
+
maybeAutoShutdown();
|
|
179
|
+
});
|
|
178
180
|
|
|
179
181
|
processMap.set(member.name, state);
|
|
180
182
|
}
|
|
@@ -224,7 +226,7 @@ function maybeAutoShutdown() {
|
|
|
224
226
|
shutdown();
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
function shutdown() {
|
|
229
|
+
async function shutdown() {
|
|
228
230
|
if (isShuttingDown) return;
|
|
229
231
|
isShuttingDown = true;
|
|
230
232
|
|
|
@@ -237,6 +239,10 @@ function shutdown() {
|
|
|
237
239
|
try { state.errWs.end(); } catch {}
|
|
238
240
|
}
|
|
239
241
|
|
|
242
|
+
try {
|
|
243
|
+
await forceCleanupTeam(teamName);
|
|
244
|
+
} catch {}
|
|
245
|
+
|
|
240
246
|
setTimeout(() => {
|
|
241
247
|
for (const state of processMap.values()) {
|
|
242
248
|
if (state.status === "running") {
|